diff --git a/lib/models/database/database.dart b/lib/models/database/database.dart index 2df41e9a..a03cdb8c 100644 --- a/lib/models/database/database.dart +++ b/lib/models/database/database.dart @@ -16,7 +16,6 @@ import 'package:spotube/models/metadata/market.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/services/kv_store/encrypted_kv_store.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; -import 'package:spotube/services/sourced_track/enums.dart'; import 'package:flutter/widgets.dart' hide Table, Key, View; import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart'; import 'package:drift/native.dart'; @@ -65,7 +64,7 @@ class AppDatabase extends _$AppDatabase { AppDatabase() : super(_openConnection()); @override - int get schemaVersion => 9; + int get schemaVersion => 10; @override MigrationStrategy get migration { diff --git a/lib/models/database/tables/preferences.dart b/lib/models/database/tables/preferences.dart index 64580330..ea2f7538 100644 --- a/lib/models/database/tables/preferences.dart +++ b/lib/models/database/tables/preferences.dart @@ -11,17 +11,6 @@ enum CloseBehavior { close, } -enum AudioSource { - youtube("YouTube"), - piped("Piped"), - jiosaavn("JioSaavn"), - invidious("Invidious"), - dabMusic("DAB Music"); - - final String label; - const AudioSource(this.label); -} - enum YoutubeClientEngine { ytDlp("yt-dlp"), youtubeExplode("YouTubeExplode"), @@ -56,8 +45,6 @@ enum SearchMode { class PreferencesTable extends Table { IntColumn get id => integer().autoIncrement()(); - TextColumn get audioQuality => textEnum() - .withDefault(Constant(SourceQualities.high.name))(); BoolColumn get albumColorSync => boolean().withDefault(const Constant(true))(); BoolColumn get amoledDarkTheme => @@ -95,14 +82,9 @@ class PreferencesTable extends Table { text().withDefault(const Constant("https://inv.nadeko.net"))(); TextColumn get themeMode => textEnum().withDefault(Constant(ThemeMode.system.name))(); - TextColumn get audioSource => - textEnum().withDefault(Constant(AudioSource.youtube.name))(); + TextColumn get audioSourceId => text().nullable()(); TextColumn get youtubeClientEngine => textEnum() .withDefault(Constant(YoutubeClientEngine.youtubeExplode.name))(); - TextColumn get streamMusicCodec => - textEnum().withDefault(Constant(SourceCodecs.weba.name))(); - TextColumn get downloadMusicCodec => - textEnum().withDefault(Constant(SourceCodecs.m4a.name))(); BoolColumn get discordPresence => boolean().withDefault(const Constant(true))(); BoolColumn get endlessPlayback => @@ -116,7 +98,6 @@ class PreferencesTable extends Table { static PreferencesTableData defaults() { return PreferencesTableData( id: 0, - audioQuality: SourceQualities.high, albumColorSync: true, amoledDarkTheme: false, checkUpdate: true, @@ -135,10 +116,8 @@ class PreferencesTable extends Table { pipedInstance: "https://pipedapi.kavin.rocks", invidiousInstance: "https://inv.nadeko.net", themeMode: ThemeMode.system, - audioSource: AudioSource.youtube, + audioSourceId: null, youtubeClientEngine: YoutubeClientEngine.youtubeExplode, - streamMusicCodec: SourceCodecs.m4a, - downloadMusicCodec: SourceCodecs.m4a, discordPresence: true, endlessPlayback: true, enableConnect: false, diff --git a/lib/models/database/tables/source_match.dart b/lib/models/database/tables/source_match.dart index fa659287..b5661137 100644 --- a/lib/models/database/tables/source_match.dart +++ b/lib/models/database/tables/source_match.dart @@ -1,26 +1,14 @@ part of '../database.dart'; -enum SourceType { - youtube._("YouTube"), - youtubeMusic._("YouTube Music"), - jiosaavn._("JioSaavn"), - dabMusic._("DAB Music"); - - final String label; - - const SourceType._(this.label); -} - @TableIndex( name: "uniq_track_match", - columns: {#trackId, #sourceId, #sourceType}, + columns: {#trackId, #sourceInfo, #sourceType}, unique: true, ) class SourceMatchTable extends Table { IntColumn get id => integer().autoIncrement()(); TextColumn get trackId => text()(); - TextColumn get sourceId => text()(); - TextColumn get sourceType => - textEnum().withDefault(Constant(SourceType.youtube.name))(); + TextColumn get sourceInfo => text()(); + TextColumn get sourceType => text()(); DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); } diff --git a/lib/models/playback/track_sources.dart b/lib/models/playback/track_sources.dart index 1666609c..262fcefa 100644 --- a/lib/models/playback/track_sources.dart +++ b/lib/models/playback/track_sources.dart @@ -1,122 +1,16 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotube/services/audio_player/audio_player.dart'; -import 'package:spotube/services/logger/logger.dart'; -import 'package:spotube/services/sourced_track/enums.dart'; part 'track_sources.freezed.dart'; part 'track_sources.g.dart'; -@freezed -class TrackSourceQuery with _$TrackSourceQuery { - TrackSourceQuery._(); - - factory TrackSourceQuery({ - required String id, - required String title, - required List artists, - required String album, - required int durationMs, - required String isrc, - required bool explicit, - }) = _TrackSourceQuery; - - factory TrackSourceQuery.fromJson(Map json) => - _$TrackSourceQueryFromJson(json); - - factory TrackSourceQuery.fromTrack(SpotubeFullTrackObject track) { - return TrackSourceQuery( - id: track.id, - title: track.name, - artists: track.artists.map((e) => e.name).toList(), - album: track.album.name, - durationMs: track.durationMs, - isrc: track.isrc, - explicit: track.explicit, - ); - } - - /// Parses [SpotubeMedia]'s [uri] property to create a [TrackSourceQuery]. - factory TrackSourceQuery.parseUri(String url) { - final isLocal = !url.startsWith("http"); - - if (isLocal) { - try { - return TrackSourceQuery( - id: url, - title: '', - artists: [], - album: '', - durationMs: 0, - isrc: '', - explicit: false, - ); - } catch (e, stackTrace) { - AppLogger.log.e( - "Failed to parse local track URI: $url\n$e", - stackTrace: stackTrace, - ); - } - } - - final uri = Uri.parse(url); - return TrackSourceQuery( - id: uri.pathSegments.last, - title: uri.queryParameters['title'] ?? '', - artists: uri.queryParameters['artists']?.split(',') ?? [], - album: uri.queryParameters['album'] ?? '', - durationMs: int.tryParse(uri.queryParameters['durationMs'] ?? '0') ?? 0, - isrc: uri.queryParameters['isrc'] ?? '', - explicit: uri.queryParameters['explicit']?.toLowerCase() == 'true', - ); - } - - String queryString() { - return toJson() - .entries - .map((e) => - "${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value is List ? e.value.join(",") : e.value.toString())}") - .join("&"); - } -} - -@freezed -class TrackSourceInfo with _$TrackSourceInfo { - factory TrackSourceInfo({ - required String id, - required String title, - required String artists, - required String thumbnail, - required String pageUrl, - required int durationMs, - }) = _TrackSourceInfo; - - factory TrackSourceInfo.fromJson(Map json) => - _$TrackSourceInfoFromJson(json); -} - -@freezed -class TrackSource with _$TrackSource { - factory TrackSource({ - required String url, - required SourceQualities quality, - required SourceCodecs codec, - required String bitrate, - required String qualityLabel, - }) = _TrackSource; - - factory TrackSource.fromJson(Map json) => - _$TrackSourceFromJson(json); -} - @JsonSerializable() class BasicSourcedTrack { - final TrackSourceQuery query; - final AudioSource source; - final TrackSourceInfo info; - final List sources; - final List siblings; + final SpotubeFullTrackObject query; + final SpotubeAudioSourceMatchObject info; + final String source; + final List sources; + final List siblings; BasicSourcedTrack({ required this.query, required this.source, diff --git a/lib/modules/library/local_folder/cache_export_dialog.dart b/lib/modules/library/local_folder/cache_export_dialog.dart index 0f10defc..fde219c9 100644 --- a/lib/modules/library/local_folder/cache_export_dialog.dart +++ b/lib/modules/library/local_folder/cache_export_dialog.dart @@ -6,7 +6,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:path/path.dart' as path; import 'package:spotube/extensions/context.dart'; import 'package:spotube/services/logger/logger.dart'; -import 'package:spotube/services/sourced_track/enums.dart'; final codecs = SourceCodecs.values.map((s) => s.name); diff --git a/lib/modules/player/player.dart b/lib/modules/player/player.dart index 4250e153..69262641 100644 --- a/lib/modules/player/player.dart +++ b/lib/modules/player/player.dart @@ -49,7 +49,7 @@ class PlayerView extends HookConsumerWidget { final activeSourceCodec = useMemoized( () { return currentActiveTrackSource - ?.getSourceOfCodec(currentActiveTrackSource.codec); + ?.getStreamOfCodec(currentActiveTrackSource.codec); }, [currentActiveTrackSource?.sources, currentActiveTrackSource?.codec], ); diff --git a/lib/modules/root/sidebar/sidebar.dart b/lib/modules/root/sidebar/sidebar.dart index e4e7db3d..1538d624 100644 --- a/lib/modules/root/sidebar/sidebar.dart +++ b/lib/modules/root/sidebar/sidebar.dart @@ -22,7 +22,7 @@ class Sidebar extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final ThemeData(:colorScheme) = Theme.of(context); - final mediaQuery = MediaQuery.of(context); + final mediaQuery = MediaQuery.sizeOf(context); final layoutMode = ref.watch(userPreferencesProvider.select((s) => s.layoutMode)); diff --git a/lib/pages/settings/sections/playback.dart b/lib/pages/settings/sections/playback.dart index 6d0b5dc3..77eaa0c5 100644 --- a/lib/pages/settings/sections/playback.dart +++ b/lib/pages/settings/sections/playback.dart @@ -23,7 +23,6 @@ import 'package:spotube/provider/audio_player/sources/piped_instances_provider.d import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; -import 'package:spotube/services/sourced_track/enums.dart'; import 'package:spotube/services/youtube_engine/yt_dlp_engine.dart'; import 'package:spotube/utils/platform.dart'; diff --git a/lib/provider/download_manager_provider.dart b/lib/provider/download_manager_provider.dart index d7f28b67..d0112765 100644 --- a/lib/provider/download_manager_provider.dart +++ b/lib/provider/download_manager_provider.dart @@ -12,7 +12,6 @@ import 'package:metadata_god/metadata_god.dart'; import 'package:path/path.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/download_manager/download_manager.dart'; -import 'package:spotube/services/sourced_track/enums.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; import 'package:spotube/utils/primitive_utils.dart'; import 'package:spotube/utils/service_utils.dart'; diff --git a/lib/provider/server/routes/playback.dart b/lib/provider/server/routes/playback.dart index 4bce7444..7155edca 100644 --- a/lib/provider/server/routes/playback.dart +++ b/lib/provider/server/routes/playback.dart @@ -20,7 +20,6 @@ import 'package:spotube/provider/server/track_sources.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/logger/logger.dart'; -import 'package:spotube/services/sourced_track/enums.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; import 'package:spotube/utils/service_utils.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart'; diff --git a/lib/provider/user_preferences/user_preferences_provider.dart b/lib/provider/user_preferences/user_preferences_provider.dart index 8e72727c..9bc64f4f 100644 --- a/lib/provider/user_preferences/user_preferences_provider.dart +++ b/lib/provider/user_preferences/user_preferences_provider.dart @@ -10,7 +10,6 @@ import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/logger/logger.dart'; -import 'package:spotube/services/sourced_track/enums.dart'; import 'package:spotube/utils/platform.dart'; import 'package:window_manager/window_manager.dart'; import 'package:open_file/open_file.dart'; diff --git a/lib/services/sourced_track/enums.dart b/lib/services/sourced_track/enums.dart deleted file mode 100644 index 9a1a5040..00000000 --- a/lib/services/sourced_track/enums.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:spotube/models/playback/track_sources.dart'; - -enum SourceCodecs { - m4a._("M4a (Best for downloaded music)"), - weba._("WebA (Best for streamed music)\nDoesn't support audio metadata"), - mp3._("MP3 (Widely supported audio format)"), - flac._("FLAC (Lossless, best quality)\nLarge file size"); - - final String label; - const SourceCodecs._(this.label); -} - -enum SourceQualities { - uncompressed(3), - high(2), - medium(1), - low(0); - - final int priority; - const SourceQualities(this.priority); - - bool operator <(SourceQualities other) { - return priority < other.priority; - } - - operator >(SourceQualities other) { - return priority > other.priority; - } -} - -typedef SiblingType = ({ - T info, - List? source -}); diff --git a/lib/services/sourced_track/models/video_info.dart b/lib/services/sourced_track/models/video_info.dart deleted file mode 100644 index e3452c61..00000000 --- a/lib/services/sourced_track/models/video_info.dart +++ /dev/null @@ -1,136 +0,0 @@ -import 'package:invidious/invidious.dart'; -import 'package:piped_client/piped_client.dart'; -import 'package:spotube/models/database/database.dart'; - -import 'package:youtube_explode_dart/youtube_explode_dart.dart'; - -class YoutubeVideoInfo { - final SearchMode searchMode; - final String title; - final Duration duration; - final String thumbnailUrl; - final String id; - final int likes; - final int dislikes; - final int views; - final String channelName; - final String channelId; - final DateTime publishedAt; - - YoutubeVideoInfo({ - required this.searchMode, - required this.title, - required this.duration, - required this.thumbnailUrl, - required this.id, - required this.likes, - required this.dislikes, - required this.views, - required this.channelName, - required this.publishedAt, - required this.channelId, - }); - - YoutubeVideoInfo.fromJson(Map json) - : title = json['title'], - searchMode = SearchMode.fromString(json['searchMode']), - duration = Duration(seconds: json['duration']), - thumbnailUrl = json['thumbnailUrl'], - id = json['id'], - likes = json['likes'], - dislikes = json['dislikes'], - views = json['views'], - channelName = json['channelName'], - channelId = json['channelId'], - publishedAt = DateTime.tryParse(json['publishedAt']) ?? DateTime.now(); - - Map toJson() => { - 'title': title, - 'duration': duration.inSeconds, - 'thumbnailUrl': thumbnailUrl, - 'id': id, - 'likes': likes, - 'dislikes': dislikes, - 'views': views, - 'channelName': channelName, - 'channelId': channelId, - 'publishedAt': publishedAt.toIso8601String(), - 'searchMode': searchMode.name, - }; - - factory YoutubeVideoInfo.fromVideo(Video video) { - return YoutubeVideoInfo( - searchMode: SearchMode.youtube, - title: video.title, - duration: video.duration ?? Duration.zero, - thumbnailUrl: video.thumbnails.mediumResUrl, - id: video.id.value, - likes: video.engagement.likeCount ?? 0, - dislikes: video.engagement.dislikeCount ?? 0, - views: video.engagement.viewCount, - channelName: video.author, - channelId: '/c/${video.channelId.value}', - publishedAt: video.uploadDate ?? DateTime(2003, 9, 9), - ); - } - - factory YoutubeVideoInfo.fromSearchItemStream( - PipedSearchItemStream searchItem, - SearchMode searchMode, - ) { - return YoutubeVideoInfo( - searchMode: searchMode, - title: searchItem.title, - duration: searchItem.duration, - thumbnailUrl: searchItem.thumbnail, - id: searchItem.id, - likes: 0, - dislikes: 0, - views: searchItem.views, - channelName: searchItem.uploaderName, - channelId: searchItem.uploaderUrl ?? "", - publishedAt: searchItem.uploadedDate != null - ? DateTime.tryParse(searchItem.uploadedDate!) ?? DateTime(2003, 9, 9) - : DateTime(2003, 9, 9), - ); - } - - factory YoutubeVideoInfo.fromStreamResponse( - PipedStreamResponse stream, SearchMode searchMode) { - return YoutubeVideoInfo( - searchMode: searchMode, - title: stream.title, - duration: stream.duration, - thumbnailUrl: stream.thumbnailUrl, - id: stream.id, - likes: stream.likes, - dislikes: stream.dislikes, - views: stream.views, - channelName: stream.uploader, - publishedAt: stream.uploadedDate != null - ? DateTime.tryParse(stream.uploadedDate!) ?? DateTime(2003, 9, 9) - : DateTime(2003, 9, 9), - channelId: stream.uploaderUrl, - ); - } - - factory YoutubeVideoInfo.fromSearchResponse( - InvidiousSearchResponseVideo searchResponse, - SearchMode searchMode, - ) { - return YoutubeVideoInfo( - searchMode: searchMode, - title: searchResponse.title, - duration: Duration(seconds: searchResponse.lengthSeconds), - thumbnailUrl: searchResponse.videoThumbnails.first.url, - id: searchResponse.videoId, - likes: 0, - dislikes: 0, - views: searchResponse.viewCount, - channelName: searchResponse.author, - channelId: searchResponse.authorId, - publishedAt: - DateTime.fromMillisecondsSinceEpoch(searchResponse.published * 1000), - ); - } -} diff --git a/lib/services/sourced_track/sourced_track.dart b/lib/services/sourced_track/sourced_track.dart index a5b2ae93..661a8447 100644 --- a/lib/services/sourced_track/sourced_track.dart +++ b/lib/services/sourced_track/sourced_track.dart @@ -1,18 +1,28 @@ -import 'package:collection/collection.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotube/models/database/database.dart'; -import 'package:spotube/models/playback/track_sources.dart'; -import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; +import 'dart:convert'; -import 'package:spotube/services/sourced_track/enums.dart'; -import 'package:spotube/services/sourced_track/sources/dab_music.dart'; -import 'package:spotube/services/sourced_track/sources/invidious.dart'; -import 'package:spotube/services/sourced_track/sources/jiosaavn.dart'; -import 'package:spotube/services/sourced_track/sources/piped.dart'; -import 'package:spotube/services/sourced_track/sources/youtube.dart'; +import 'package:collection/collection.dart'; +import 'package:dio/dio.dart'; +import 'package:drift/drift.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:spotube/extensions/string.dart'; +import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/models/playback/track_sources.dart'; +import 'package:spotube/provider/database/database.dart'; +import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; +import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; +import 'package:spotube/services/dio/dio.dart'; +import 'package:spotube/services/logger/logger.dart'; + +import 'package:spotube/services/sourced_track/exceptions.dart'; import 'package:spotube/utils/service_utils.dart'; -abstract class SourcedTrack extends BasicSourcedTrack { +final officialMusicRegex = RegExp( + r"official\s(video|audio|music\svideo|lyric\svideo|visualizer)", + caseSensitive: false, +); + +class SourcedTrack extends BasicSourcedTrack { final Ref ref; SourcedTrack({ @@ -24,72 +34,10 @@ abstract class SourcedTrack extends BasicSourcedTrack { required super.sources, }); - static SourcedTrack fromJson( - Map json, { - required Ref ref, - }) { - final preferences = ref.read(userPreferencesProvider); - - final info = TrackSourceInfo.fromJson(json["info"]); - final query = TrackSourceQuery.fromJson(json["query"]); - final source = AudioSource.values.firstWhereOrNull( - (source) => source.name == json["source"], - ) ?? - preferences.audioSource; - final siblings = (json["siblings"] as List) - .map((s) => TrackSourceInfo.fromJson(s)) - .toList(); - final sources = - (json["sources"] as List).map((s) => TrackSource.fromJson(s)).toList(); - - return switch (preferences.audioSource) { - AudioSource.youtube => YoutubeSourcedTrack( - ref: ref, - source: source, - siblings: siblings, - info: info, - query: query, - sources: sources, - ), - AudioSource.piped => PipedSourcedTrack( - ref: ref, - source: source, - siblings: siblings, - info: info, - query: query, - sources: sources, - ), - AudioSource.jiosaavn => JioSaavnSourcedTrack( - ref: ref, - source: source, - siblings: siblings, - info: info, - query: query, - sources: sources, - ), - AudioSource.invidious => InvidiousSourcedTrack( - ref: ref, - source: source, - siblings: siblings, - info: info, - query: query, - sources: sources, - ), - AudioSource.dabMusic => DABMusicSourcedTrack( - ref: ref, - source: source, - siblings: siblings, - info: info, - query: query, - sources: sources, - ), - }; - } - - static String getSearchTerm(TrackSourceQuery track) { + static String getSearchTerm(SpotubeFullTrackObject track) { final title = ServiceUtils.getTitle( - track.title, - artists: track.artists, + track.name, + artists: track.artists.map((e) => e.name).toList(), onlyCleanArtist: true, ).trim(); @@ -99,61 +47,256 @@ abstract class SourcedTrack extends BasicSourcedTrack { } static Future fetchFromQuery({ - required TrackSourceQuery query, + required SpotubeFullTrackObject query, required Ref ref, }) async { - final preferences = ref.read(userPreferencesProvider); - try { - return switch (preferences.audioSource) { - AudioSource.youtube => - await YoutubeSourcedTrack.fetchFromTrack(query: query, ref: ref), - AudioSource.piped => - await PipedSourcedTrack.fetchFromTrack(query: query, ref: ref), - AudioSource.invidious => - await InvidiousSourcedTrack.fetchFromTrack(query: query, ref: ref), - AudioSource.jiosaavn => - await JioSaavnSourcedTrack.fetchFromTrack(query: query, ref: ref), - AudioSource.dabMusic => - await DABMusicSourcedTrack.fetchFromTrack(query: query, ref: ref), - }; - } catch (e) { - if (preferences.audioSource == AudioSource.youtube) { - rethrow; + final audioSource = await ref.read(audioSourcePluginProvider.future); + final audioSourceConfig = await ref.read(metadataPluginsProvider + .selectAsync((data) => data.defaultAudioSourcePluginConfig)); + if (audioSource == null || audioSourceConfig == null) { + throw Exception("Dude wat?"); + } + + final database = ref.read(databaseProvider); + final cachedSource = await (database.select(database.sourceMatchTable) + ..where((s) => + s.trackId.equals(query.id) & + s.sourceType.equals(audioSourceConfig.slug)) + ..limit(1) + ..orderBy([ + (s) => + OrderingTerm(expression: s.createdAt, mode: OrderingMode.desc), + ])) + .get() + .then((s) => s.firstOrNull); + + if (cachedSource == null) { + final siblings = await fetchSiblings(ref: ref, query: query); + if (siblings.isEmpty) { + throw TrackNotFoundError(query); } - return await YoutubeSourcedTrack.fetchFromTrack(query: query, ref: ref); + await database.into(database.sourceMatchTable).insert( + SourceMatchTableCompanion.insert( + trackId: query.id, + source: jsonEncode(siblings.first), + sourceType: Value(audioSourceConfig.slug), + ), + ); + + return SourcedTrack( + ref: ref, + siblings: siblings.map((s) => s.info).skip(1).toList(), + info: siblings.first.info, + source: audioSourceConfig.slug, + sources: siblings.first.source ?? [], + query: query, + ); } + final item = + SpotubeAudioSourceMatchObject.fromJson(jsonDecode(cachedSource.source)); + final manifest = await audioSource.audioSource.streams(item); + + final sourcedTrack = SourcedTrack( + ref: ref, + siblings: [], + sources: manifest, + info: item, + query: query, + source: audioSourceConfig.slug, + ); + + AppLogger.log.i("${query.name}: ${sourcedTrack.url}"); + + return sourcedTrack; } - static Future> fetchSiblings({ - required TrackSourceQuery query, + static List rankResults( + List results, + SpotubeFullTrackObject track, + ) { + return results + .map((sibling) { + int score = 0; + + for (final artist in track.artists) { + final isSameChannelArtist = + sibling.artists.any((a) => a.toLowerCase() == artist.name); + + if (isSameChannelArtist) { + score += 1; + } + + final titleContainsArtist = + sibling.title.toLowerCase().contains(artist.name.toLowerCase()); + + if (titleContainsArtist) { + score += 1; + } + } + + final titleContainsTrackName = + sibling.title.toLowerCase().contains(track.name.toLowerCase()); + + final hasOfficialFlag = + officialMusicRegex.hasMatch(sibling.title.toLowerCase()); + + if (titleContainsTrackName) { + score += 3; + } + + if (hasOfficialFlag) { + score += 1; + } + + if (hasOfficialFlag && titleContainsTrackName) { + score += 2; + } + + return (sibling: sibling, score: score); + }) + .sorted((a, b) => b.score.compareTo(a.score)) + .map((e) => e.sibling) + .toList(); + } + + static Future> fetchSiblings({ + required SpotubeFullTrackObject query, required Ref ref, - }) { - final preferences = ref.read(userPreferencesProvider); + }) async { + final audioSource = await ref.read(audioSourcePluginProvider.future); - return switch (preferences.audioSource) { - AudioSource.piped => - PipedSourcedTrack.fetchSiblings(query: query, ref: ref), - AudioSource.youtube => - YoutubeSourcedTrack.fetchSiblings(query: query, ref: ref), - AudioSource.jiosaavn => - JioSaavnSourcedTrack.fetchSiblings(query: query, ref: ref), - AudioSource.invidious => - InvidiousSourcedTrack.fetchSiblings(query: query, ref: ref), - AudioSource.dabMusic => - DABMusicSourcedTrack.fetchSiblings(query: query, ref: ref), - }; + if (audioSource == null) { + throw Exception("Dude wat?"); + } + + final videoResults = []; + + final searchResults = await audioSource.audioSource.matches(query); + + if (ServiceUtils.onlyContainsEnglish(query.name)) { + videoResults.addAll(searchResults); + } else { + videoResults.addAll(rankResults(searchResults, query)); + } + + return videoResults.toSet().toList(); } - Future copyWithSibling(); + Future copyWithSibling() async { + if (siblings.isNotEmpty) { + return this; + } + final fetchedSiblings = await fetchSiblings(ref: ref, query: query); - Future swapWithSibling(TrackSourceInfo sibling); + return SourcedTrack( + ref: ref, + siblings: fetchedSiblings.where((s) => s.id != info.id).toList(), + source: source, + sources: sources, + info: info, + query: query, + ); + } + + Future swapWithSibling( + SpotubeAudioSourceMatchObject sibling) async { + if (sibling.id == info.id) { + return null; + } + + final audioSource = await ref.read(audioSourcePluginProvider.future); + final audioSourceConfig = await ref.read(metadataPluginsProvider + .selectAsync((data) => data.defaultAudioSourcePluginConfig)); + if (audioSource == null || audioSourceConfig == null) { + throw Exception("Dude wat?"); + } + + // a sibling source that was fetched from the search results + final isStepSibling = siblings.none((s) => s.id == sibling.id); + + final newSourceInfo = isStepSibling + ? sibling + : siblings.firstWhere((s) => s.id == sibling.id); + + final newSiblings = siblings.where((s) => s.id != sibling.id).toList() + ..insert(0, info); + + final manifest = await audioSource.audioSource.streams(newSourceInfo); + + final database = ref.read(databaseProvider); + + await database.into(database.sourceMatchTable).insert( + SourceMatchTableCompanion.insert( + trackId: query.id, + source: jsonEncode(siblings.first), + sourceType: Value(audioSourceConfig.slug), + createdAt: Value(DateTime.now()), + ), + mode: InsertMode.replace, + ); + + return SourcedTrack( + ref: ref, + source: source, + siblings: newSiblings, + sources: manifest, + info: newSourceInfo, + query: query, + ); + } Future swapWithSiblingOfIndex(int index) { return swapWithSibling(siblings[index]); } - Future refreshStream(); + Future refreshStream() async { + final audioSource = await ref.read(audioSourcePluginProvider.future); + final audioSourceConfig = await ref.read(metadataPluginsProvider + .selectAsync((data) => data.defaultAudioSourcePluginConfig)); + if (audioSource == null || audioSourceConfig == null) { + throw Exception("Dude wat?"); + } + + List validStreams = []; + + final stringBuffer = StringBuffer(); + for (final source in sources) { + final res = await globalDio.head( + source.url, + options: + Options(validateStatus: (status) => status != null && status < 500), + ); + + stringBuffer.writeln( + "[${query.id}] ${res.statusCode} ${source.container} ${source.codec} ${source.bitrate}", + ); + + if (res.statusCode! < 400) { + validStreams.add(source); + } + } + + AppLogger.log.d(stringBuffer.toString()); + + if (validStreams.isEmpty) { + validStreams = await audioSource.audioSource.streams(info); + } + + final sourcedTrack = SourcedTrack( + ref: ref, + siblings: siblings, + source: source, + sources: validStreams, + info: info, + query: query, + ); + + AppLogger.log.i("Refreshing ${query.name}: ${sourcedTrack.url}"); + + return sourcedTrack; + } + String? get url { final preferences = ref.read(userPreferencesProvider); @@ -170,58 +313,75 @@ abstract class SourcedTrack extends BasicSourcedTrack { /// /// If no sources match the codec, it will return the first or last source /// based on the user's audio quality preference. - TrackSource? getSourceOfCodec(SourceCodecs codec) { - final preferences = ref.read(userPreferencesProvider); + SpotubeAudioSourceStreamObject? getStreamOfQuality( + SpotubeAudioSourceContainerPreset preset, + int qualityIndex, + ) { + final quality = preset.qualities[qualityIndex]; final exactMatch = sources.firstWhereOrNull( - (source) => - source.codec == codec && source.quality == preferences.audioQuality, + (source) { + if (source.container != preset.name) return false; + + if (quality case SpotubeAudioLosslessContainerQuality()) { + return source.sampleRate == quality.sampleRate && + source.bitDepth == quality.bitDepth; + } else { + return source.bitrate == + (preset as SpotubeAudioLossyContainerQuality).bitrate; + } + }, ); if (exactMatch != null) { return exactMatch; } - final sameCodecSources = sources - .where((source) => source.codec == codec) - .toList() - .sorted((a, b) { - final aDiff = (a.quality.index - preferences.audioQuality.index).abs(); - final bDiff = (b.quality.index - preferences.audioQuality.index).abs(); - return aDiff != bDiff ? aDiff - bDiff : a.quality.index - b.quality.index; - }).toList(); + // Find the closest to preset + SpotubeAudioSourceStreamObject? closest; + for (final source in sources) { + if (source.container != preset.name) continue; - if (sameCodecSources.isNotEmpty) { - return preferences.audioQuality > SourceQualities.low - ? sameCodecSources.first - : sameCodecSources.last; + if (quality case SpotubeAudioLosslessContainerQuality()) { + final sourceBps = (source.bitDepth ?? 0) * (source.sampleRate ?? 0); + final qualityBps = quality.bitDepth * quality.sampleRate; + final closestBps = + (closest?.bitDepth ?? 0) * (closest?.sampleRate ?? 0); + + if (sourceBps == qualityBps) { + closest = source; + break; + } + final closestDiff = (closestBps - qualityBps).abs(); + final sourceDiff = (sourceBps - qualityBps).abs(); + + if (sourceDiff < closestDiff) { + closest = source; + } + } else { + final presetBitrate = + (preset as SpotubeAudioLossyContainerQuality).bitrate; + if (presetBitrate == source.bitrate) { + closest = source; + break; + } + + final closestDiff = (closest?.bitrate ?? 0) - presetBitrate; + final sourceDiff = (source.bitrate ?? 0) - presetBitrate; + + if (sourceDiff < closestDiff) { + closest = source; + } + } } - final fallbackSource = sources.sorted((a, b) { - final aDiff = (a.quality.index - preferences.audioQuality.index).abs(); - final bDiff = (b.quality.index - preferences.audioQuality.index).abs(); - return aDiff != bDiff ? aDiff - bDiff : a.quality.index - b.quality.index; - }); - - return preferences.audioQuality > SourceQualities.low - ? fallbackSource.firstOrNull - : fallbackSource.lastOrNull; + return closest; } - String? getUrlOfCodec(SourceCodecs codec) { - return getSourceOfCodec(codec)?.url; - } - - SourceCodecs get codec { - final preferences = ref.read(userPreferencesProvider); - - return switch (preferences.audioSource) { - AudioSource.dabMusic => - preferences.audioQuality == SourceQualities.uncompressed - ? SourceCodecs.flac - : SourceCodecs.mp3, - AudioSource.jiosaavn => SourceCodecs.m4a, - _ => preferences.streamMusicCodec - }; + String? getUrlOfQuality( + SpotubeAudioSourceContainerPreset preset, + int qualityIndex, + ) { + return getStreamOfQuality(preset, qualityIndex)?.url; } } diff --git a/lib/services/sourced_track/sources/dab_music.dart b/lib/services/sourced_track/sources/dab_music.dart deleted file mode 100644 index 83cc55b4..00000000 --- a/lib/services/sourced_track/sources/dab_music.dart +++ /dev/null @@ -1,303 +0,0 @@ -import 'dart:convert'; - -import 'package:collection/collection.dart'; -import 'package:dio/dio.dart'; -import 'package:drift/drift.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotube/models/database/database.dart'; -import 'package:spotube/models/playback/track_sources.dart'; -import 'package:spotube/provider/database/database.dart'; -import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; -import 'package:spotube/services/logger/logger.dart'; -import 'package:spotube/services/sourced_track/enums.dart'; -import 'package:spotube/services/sourced_track/exceptions.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; -import 'package:dab_music_api/dab_music_api.dart'; - -final dabMusicApiClient = DabMusicApiClient( - Dio(), - baseUrl: "https://dab.yeet.su/api", -); - -/// Only Music source that can't support database caching due to having no endpoint. -/// But ISRC search is 100% reliable so caching is actually not necessary. -class DABMusicSourcedTrack extends SourcedTrack { - DABMusicSourcedTrack({ - required super.ref, - required super.source, - required super.siblings, - required super.info, - required super.query, - required super.sources, - }); - - static Future fetchFromTrack({ - required TrackSourceQuery query, - required Ref ref, - }) async { - try { - final database = ref.read(databaseProvider); - final cachedSource = await (database.select(database.sourceMatchTable) - ..where((s) => s.trackId.equals(query.id)) - ..limit(1) - ..orderBy([ - (s) => OrderingTerm( - expression: s.createdAt, - mode: OrderingMode.desc, - ), - ])) - .get() - .then((s) => s.firstOrNull); - - if (cachedSource != null && - cachedSource.sourceType == SourceType.dabMusic) { - final json = jsonDecode(cachedSource.sourceId); - final info = TrackSourceInfo.fromJson(json["info"]); - final source = (json["sources"] as List?) - ?.map((s) => TrackSource.fromJson(s)) - .toList(); - - final [updatedSource] = await fetchSources( - info.id, - ref.read(userPreferencesProvider).audioQuality, - const AudioQuality( - isHiRes: true, - maximumBitDepth: 16, - maximumSamplingRate: 44.1, - ), - ); - - return DABMusicSourcedTrack( - ref: ref, - source: AudioSource.dabMusic, - siblings: [], - info: info, - query: query, - sources: [ - source!.first.copyWith(url: updatedSource.url), - ], - ); - } - - final siblings = await fetchSiblings(ref: ref, query: query); - - if (siblings.isEmpty) { - throw TrackNotFoundError(query); - } - - await database.into(database.sourceMatchTable).insert( - SourceMatchTableCompanion.insert( - trackId: query.id, - sourceId: jsonEncode({ - "info": siblings.first.info.toJson(), - "sources": (siblings.first.source ?? []) - .map((s) => s.toJson()) - .toList(), - }), - sourceType: const Value(SourceType.dabMusic), - ), - ); - - return DABMusicSourcedTrack( - ref: ref, - siblings: siblings.map((s) => s.info).skip(1).toList(), - sources: siblings.first.source!, - info: siblings.first.info, - query: query, - source: AudioSource.dabMusic, - ); - } catch (e, stackTrace) { - AppLogger.reportError(e, stackTrace); - rethrow; - } - } - - static Future> fetchSources( - String id, - SourceQualities quality, - AudioQuality trackMaximumQuality, - ) async { - try { - final isUncompressed = quality == SourceQualities.uncompressed; - final streamResponse = await dabMusicApiClient.music.getStream( - trackId: id, - quality: isUncompressed ? "27" : "5", - ); - if (streamResponse.url == null) { - throw Exception("No stream URL found for track ID: $id"); - } - - // kbps = (bitDepth * sampleRate * channels) / 1000 - final uncompressedBitrate = !isUncompressed - ? 0 - : ((trackMaximumQuality.maximumBitDepth ?? 0) * - ((trackMaximumQuality.maximumSamplingRate ?? 0) * 1000) * - 2) / - 1000; - return [ - TrackSource( - url: streamResponse.url!, - quality: isUncompressed - ? SourceQualities.uncompressed - : SourceQualities.high, - bitrate: - isUncompressed ? "${uncompressedBitrate.floor()}kbps" : "320kbps", - codec: isUncompressed ? SourceCodecs.flac : SourceCodecs.mp3, - qualityLabel: isUncompressed - ? "${trackMaximumQuality.maximumBitDepth}bit • ${trackMaximumQuality.maximumSamplingRate}kHz • FLAC • Stereo" - : "MP3 • 320kbps • mp3 • Stereo", - ), - ]; - } catch (e, stackTrace) { - AppLogger.reportError(e, stackTrace); - rethrow; - } - } - - static Future toSiblingType( - Ref ref, - int index, - Track result, - ) async { - try { - List? source; - if (index == 0) { - source = await fetchSources( - result.id.toString(), - ref.read(userPreferencesProvider).audioQuality, - result.audioQuality!, - ); - } - - final SiblingType sibling = ( - info: TrackSourceInfo( - artists: result.artist!, - durationMs: Duration(seconds: result.duration!).inMilliseconds, - id: result.id.toString(), - pageUrl: "https://dab.yeet.su/music/${result.id}", - thumbnail: result.albumCover!, - title: result.title!, - ), - source: source, - ); - - return sibling; - } catch (e, stackTrace) { - AppLogger.reportError(e, stackTrace); - rethrow; - } - } - - static Future> fetchSiblings({ - required TrackSourceQuery query, - required Ref ref, - }) async { - try { - List results = []; - - if (query.isrc.isNotEmpty) { - final res = - await dabMusicApiClient.music.getSearch(q: query.isrc, limit: 1); - results = res.tracks ?? []; - } - - if (results.isEmpty) { - final res = await dabMusicApiClient.music.getSearch( - q: SourcedTrack.getSearchTerm(query), - limit: 5, - ); - results = res.tracks ?? []; - } - - if (results.isEmpty) { - return []; - } - - final matchedResults = - results.mapIndexed((index, d) => toSiblingType(ref, index, d)); - - return Future.wait(matchedResults); - } catch (e, stackTrace) { - AppLogger.reportError(e, stackTrace); - rethrow; - } - } - - @override - Future copyWithSibling() async { - if (siblings.isNotEmpty) { - return this; - } - final fetchedSiblings = await fetchSiblings(ref: ref, query: query); - - return DABMusicSourcedTrack( - ref: ref, - siblings: fetchedSiblings - .where((s) => s.info.id != info.id) - .map((s) => s.info) - .toList(), - source: source, - info: info, - query: query, - sources: sources, - ); - } - - @override - Future swapWithSibling(TrackSourceInfo sibling) async { - if (sibling.id == this.info.id) { - return null; - } - - // a sibling source that was fetched from the search results - final isStepSibling = siblings.none((s) => s.id == sibling.id); - - final newSourceInfo = isStepSibling - ? sibling - : siblings.firstWhere((s) => s.id == sibling.id); - final newSiblings = siblings.where((s) => s.id != sibling.id).toList() - ..insert(0, this.info); - - final source = await fetchSources( - sibling.id, - ref.read(userPreferencesProvider).audioQuality, - const AudioQuality( - isHiRes: true, - maximumBitDepth: 16, - maximumSamplingRate: 44.1, - ), - ); - - final database = ref.read(databaseProvider); - - await database.into(database.sourceMatchTable).insert( - SourceMatchTableCompanion.insert( - trackId: query.id, - sourceId: jsonEncode({ - "info": newSourceInfo.toJson(), - "sources": source.map((s) => s.toJson()).toList(), - }), - sourceType: const Value(SourceType.dabMusic), - // Because we're sorting by createdAt in the query - // we have to update it to indicate priority - createdAt: Value(DateTime.now()), - ), - mode: InsertMode.replace, - ); - - return DABMusicSourcedTrack( - ref: ref, - siblings: newSiblings, - sources: source, - info: newSourceInfo, - query: query, - source: AudioSource.dabMusic, - ); - } - - @override - Future refreshStream() async { - // There's no need to refresh the stream for DABMusicSourcedTrack - return this; - } -} diff --git a/lib/services/sourced_track/sources/invidious.dart b/lib/services/sourced_track/sources/invidious.dart deleted file mode 100644 index c5421355..00000000 --- a/lib/services/sourced_track/sources/invidious.dart +++ /dev/null @@ -1,263 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:drift/drift.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotube/models/database/database.dart'; -import 'package:spotube/models/playback/track_sources.dart'; -import 'package:spotube/provider/database/database.dart'; -import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; -import 'package:spotube/services/sourced_track/enums.dart'; -import 'package:spotube/services/sourced_track/exceptions.dart'; -import 'package:spotube/services/sourced_track/models/video_info.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; -import 'package:invidious/invidious.dart'; -import 'package:spotube/services/sourced_track/sources/youtube.dart'; -import 'package:spotube/utils/service_utils.dart'; - -final invidiousProvider = Provider( - (ref) { - final invidiousInstance = ref.watch( - userPreferencesProvider.select((s) => s.invidiousInstance), - ); - return InvidiousClient(server: invidiousInstance); - }, -); - -class InvidiousSourcedTrack extends SourcedTrack { - InvidiousSourcedTrack({ - required super.ref, - required super.source, - required super.siblings, - required super.info, - required super.query, - required super.sources, - }); - - static Future fetchFromTrack({ - required TrackSourceQuery query, - required Ref ref, - }) async { - final audioSource = ref.read(userPreferencesProvider).audioSource; - final database = ref.read(databaseProvider); - final cachedSource = await (database.select(database.sourceMatchTable) - ..where((s) => s.trackId.equals(query.id)) - ..limit(1) - ..orderBy([ - (s) => - OrderingTerm(expression: s.createdAt, mode: OrderingMode.desc), - ])) - .getSingleOrNull(); - final invidiousClient = ref.read(invidiousProvider); - - if (cachedSource == null) { - final siblings = await fetchSiblings(ref: ref, query: query); - if (siblings.isEmpty) { - throw TrackNotFoundError(query); - } - - await database.into(database.sourceMatchTable).insert( - SourceMatchTableCompanion.insert( - trackId: query.id, - sourceId: siblings.first.info.id, - sourceType: const Value(SourceType.youtube), - ), - ); - - return InvidiousSourcedTrack( - ref: ref, - siblings: siblings.map((s) => s.info).skip(1).toList(), - sources: siblings.first.source as List, - info: siblings.first.info, - query: query, - source: audioSource, - ); - } else { - final manifest = - await invidiousClient.videos.get(cachedSource.sourceId, local: true); - - return InvidiousSourcedTrack( - ref: ref, - siblings: [], - sources: toSources(manifest), - info: TrackSourceInfo( - id: manifest.videoId, - artists: manifest.author, - pageUrl: "https://www.youtube.com/watch?v=${manifest.videoId}", - thumbnail: manifest.videoThumbnails.first.url, - title: manifest.title, - durationMs: Duration(seconds: manifest.lengthSeconds).inMilliseconds, - ), - query: query, - source: audioSource, - ); - } - } - - static List toSources(InvidiousVideoResponse manifest) { - return manifest.adaptiveFormats.map((stream) { - var isWebm = stream.type.contains("audio/webm"); - return TrackSource( - url: stream.url.toString(), - quality: switch (stream.qualityLabel) { - "high" => SourceQualities.high, - "medium" => SourceQualities.medium, - _ => SourceQualities.low, - }, - codec: isWebm ? SourceCodecs.weba : SourceCodecs.m4a, - bitrate: stream.bitrate, - qualityLabel: - "${isWebm ? "Opus" : "AAC"} • ${stream.bitrate.replaceAll("kbps", "")}kbps " - "• ${isWebm ? "weba" : "m4a"} • Stereo", - ); - }).toList(); - } - - static Future toSiblingType( - int index, - YoutubeVideoInfo item, - InvidiousClient invidiousClient, - ) async { - List? sourceMap; - if (index == 0) { - final manifest = await invidiousClient.videos.get(item.id, local: true); - sourceMap = toSources(manifest); - } - - final SiblingType sibling = ( - info: TrackSourceInfo( - id: item.id, - artists: item.channelName, - pageUrl: "https://www.youtube.com/watch?v=${item.id}", - thumbnail: item.thumbnailUrl, - title: item.title, - durationMs: item.duration.inMilliseconds, - ), - source: sourceMap, - ); - - return sibling; - } - - static Future> fetchSiblings({ - required TrackSourceQuery query, - required Ref ref, - }) async { - final invidiousClient = ref.read(invidiousProvider); - final preference = ref.read(userPreferencesProvider); - - final searchQuery = SourcedTrack.getSearchTerm(query); - - final searchResults = await invidiousClient.search.list( - searchQuery, - type: InvidiousSearchType.video, - ); - - if (ServiceUtils.onlyContainsEnglish(searchQuery)) { - return await Future.wait( - searchResults - .whereType() - .map( - (result) => YoutubeVideoInfo.fromSearchResponse( - result, - preference.searchMode, - ), - ) - .mapIndexed((i, r) => toSiblingType(i, r, invidiousClient)), - ); - } - - final rankedSiblings = YoutubeSourcedTrack.rankResults( - searchResults - .whereType() - .map( - (result) => YoutubeVideoInfo.fromSearchResponse( - result, - preference.searchMode, - ), - ) - .toList(), - query, - ); - - return await Future.wait( - rankedSiblings.mapIndexed((i, r) => toSiblingType(i, r, invidiousClient)), - ); - } - - @override - Future copyWithSibling() async { - if (siblings.isNotEmpty) { - return this; - } - final fetchedSiblings = await fetchSiblings(ref: ref, query: query); - - return InvidiousSourcedTrack( - ref: ref, - siblings: fetchedSiblings - .where((s) => s.info.id != info.id) - .map((s) => s.info) - .toList(), - source: source, - info: info, - query: query, - sources: sources, - ); - } - - @override - Future swapWithSibling(TrackSourceInfo sibling) async { - if (sibling.id == info.id) { - return null; - } - - // a sibling source that was fetched from the search results - final isStepSibling = siblings.none((s) => s.id == sibling.id); - - final newSourceInfo = isStepSibling - ? sibling - : siblings.firstWhere((s) => s.id == sibling.id); - final newSiblings = siblings.where((s) => s.id != sibling.id).toList() - ..insert(0, info); - - final pipedClient = ref.read(invidiousProvider); - - final manifest = - await pipedClient.videos.get(newSourceInfo.id, local: true); - - final database = ref.read(databaseProvider); - await database.into(database.sourceMatchTable).insert( - SourceMatchTableCompanion.insert( - trackId: query.id, - sourceId: newSourceInfo.id, - sourceType: const Value(SourceType.youtube), - // Because we're sorting by createdAt in the query - // we have to update it to indicate priority - createdAt: Value(DateTime.now()), - ), - mode: InsertMode.replace, - ); - - return InvidiousSourcedTrack( - ref: ref, - siblings: newSiblings, - sources: toSources(manifest), - info: newSourceInfo, - query: query, - source: source, - ); - } - - @override - Future refreshStream() async { - final manifest = - await ref.read(invidiousProvider).videos.get(info.id, local: true); - - return InvidiousSourcedTrack( - ref: ref, - siblings: siblings, - sources: toSources(manifest), - info: info, - query: query, - source: source, - ); - } -} diff --git a/lib/services/sourced_track/sources/jiosaavn.dart b/lib/services/sourced_track/sources/jiosaavn.dart deleted file mode 100644 index be78be25..00000000 --- a/lib/services/sourced_track/sources/jiosaavn.dart +++ /dev/null @@ -1,231 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:drift/drift.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotube/models/database/database.dart'; -import 'package:spotube/models/playback/track_sources.dart'; -import 'package:spotube/provider/database/database.dart'; -import 'package:spotube/services/sourced_track/enums.dart'; -import 'package:spotube/services/sourced_track/exceptions.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; -import 'package:jiosaavn/jiosaavn.dart'; -import 'package:spotube/extensions/string.dart'; - -final jiosaavnClient = JioSaavnClient(); - -class JioSaavnSourcedTrack extends SourcedTrack { - JioSaavnSourcedTrack({ - required super.ref, - required super.source, - required super.siblings, - required super.info, - required super.query, - required super.sources, - }); - - static Future fetchFromTrack({ - required TrackSourceQuery query, - required Ref ref, - bool weakMatch = false, - }) async { - final database = ref.read(databaseProvider); - final cachedSource = await (database.select(database.sourceMatchTable) - ..where((s) => s.trackId.equals(query.id)) - ..limit(1) - ..orderBy([ - (s) => - OrderingTerm(expression: s.createdAt, mode: OrderingMode.desc), - ])) - .getSingleOrNull(); - - if (cachedSource == null || - cachedSource.sourceType != SourceType.jiosaavn) { - final siblings = - await fetchSiblings(ref: ref, query: query, weakMatch: weakMatch); - - if (siblings.isEmpty) { - throw TrackNotFoundError(query); - } - - await database.into(database.sourceMatchTable).insert( - SourceMatchTableCompanion.insert( - trackId: query.id, - sourceId: siblings.first.info.id, - sourceType: const Value(SourceType.jiosaavn), - ), - ); - - return JioSaavnSourcedTrack( - ref: ref, - siblings: siblings.map((s) => s.info).skip(1).toList(), - sources: siblings.first.source!, - info: siblings.first.info, - query: query, - source: AudioSource.jiosaavn, - ); - } - - final [item] = - await jiosaavnClient.songs.detailsById([cachedSource.sourceId]); - - final (:info, :source) = toSiblingType(item); - - return JioSaavnSourcedTrack( - ref: ref, - siblings: [], - sources: source!, - query: query, - info: info, - source: AudioSource.jiosaavn, - ); - } - - static SiblingType toSiblingType(SongResponse result) { - final SiblingType sibling = ( - info: TrackSourceInfo( - artists: [ - result.primaryArtists, - if (result.featuredArtists.isNotEmpty) ", ", - result.featuredArtists - ].join("").unescapeHtml(), - durationMs: - Duration(seconds: int.parse(result.duration)).inMilliseconds, - id: result.id, - pageUrl: result.url, - thumbnail: result.image?.last.link ?? "", - title: result.name!.unescapeHtml(), - ), - source: result.downloadUrl!.map((link) { - return TrackSource( - url: link.link, - quality: link.quality == "320kbps" - ? SourceQualities.high - : link.quality == "160kbps" - ? SourceQualities.medium - : SourceQualities.low, - codec: SourceCodecs.m4a, - bitrate: link.quality, - qualityLabel: "AAC • ${link.quality} • MP4 • Stereo", - ); - }).toList() - ); - - return sibling; - } - - static Future> fetchSiblings({ - required TrackSourceQuery query, - required Ref ref, - bool weakMatch = false, - }) async { - final searchQuery = SourcedTrack.getSearchTerm(query); - - final SongSearchResponse(:results) = - await jiosaavnClient.search.songs(searchQuery, limit: 20); - - final trackArtistNames = query.artists; - - final matchedResults = results - .where( - (s) { - s.name?.unescapeHtml().contains(query.title) ?? false; - - final sameName = s.name?.unescapeHtml() == query.title; - final artistNames = [ - s.primaryArtists, - if (s.featuredArtists.isNotEmpty) ", ", - s.featuredArtists - ].join("").unescapeHtml(); - final sameArtists = artistNames.split(", ").any( - (artist) => trackArtistNames.any((ar) => artist == ar), - ); - if (weakMatch) { - final containsName = - s.name?.unescapeHtml().contains(query.title) ?? false; - final containsPrimaryArtist = s.primaryArtists - .unescapeHtml() - .contains(trackArtistNames.first); - - return containsName && containsPrimaryArtist; - } - - return sameName && sameArtists; - }, - ) - .map(toSiblingType) - .toList(); - - if (weakMatch && matchedResults.isEmpty) { - return results.map(toSiblingType).toList(); - } - - return matchedResults; - } - - @override - Future copyWithSibling() async { - if (siblings.isNotEmpty) { - return this; - } - final fetchedSiblings = await fetchSiblings(ref: ref, query: query); - - return JioSaavnSourcedTrack( - ref: ref, - siblings: fetchedSiblings - .where((s) => s.info.id != info.id) - .map((s) => s.info) - .toList(), - source: source, - info: info, - query: query, - sources: sources, - ); - } - - @override - Future swapWithSibling(TrackSourceInfo sibling) async { - if (sibling.id == this.info.id) { - return null; - } - - // a sibling source that was fetched from the search results - final isStepSibling = siblings.none((s) => s.id == sibling.id); - - final newSourceInfo = isStepSibling - ? sibling - : siblings.firstWhere((s) => s.id == sibling.id); - final newSiblings = siblings.where((s) => s.id != sibling.id).toList() - ..insert(0, this.info); - - final [item] = await jiosaavnClient.songs.detailsById([newSourceInfo.id]); - - final (:info, :source) = toSiblingType(item); - - final database = ref.read(databaseProvider); - await database.into(database.sourceMatchTable).insert( - SourceMatchTableCompanion.insert( - trackId: query.id, - sourceId: info.id, - sourceType: const Value(SourceType.jiosaavn), - // Because we're sorting by createdAt in the query - // we have to update it to indicate priority - createdAt: Value(DateTime.now()), - ), - mode: InsertMode.replace, - ); - - return JioSaavnSourcedTrack( - ref: ref, - siblings: newSiblings, - sources: source!, - info: newSourceInfo, - query: query, - source: AudioSource.jiosaavn, - ); - } - - @override - Future refreshStream() async { - // There's no need to refresh the stream for JioSaavnSourcedTrack - return this; - } -} diff --git a/lib/services/sourced_track/sources/piped.dart b/lib/services/sourced_track/sources/piped.dart deleted file mode 100644 index fca6c623..00000000 --- a/lib/services/sourced_track/sources/piped.dart +++ /dev/null @@ -1,292 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:drift/drift.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:piped_client/piped_client.dart'; -import 'package:spotube/models/database/database.dart'; -import 'package:spotube/models/playback/track_sources.dart'; -import 'package:spotube/provider/database/database.dart'; -import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; - -import 'package:spotube/services/sourced_track/enums.dart'; -import 'package:spotube/services/sourced_track/exceptions.dart'; -import 'package:spotube/services/sourced_track/models/video_info.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; -import 'package:spotube/services/sourced_track/sources/youtube.dart'; -import 'package:spotube/utils/service_utils.dart'; - -final pipedProvider = Provider( - (ref) { - final instance = - ref.watch(userPreferencesProvider.select((s) => s.pipedInstance)); - return PipedClient(instance: instance); - }, -); - -class PipedSourcedTrack extends SourcedTrack { - PipedSourcedTrack({ - required super.ref, - required super.source, - required super.siblings, - required super.info, - required super.query, - required super.sources, - }); - - static Future fetchFromTrack({ - required TrackSourceQuery query, - required Ref ref, - }) async { - final audioSource = ref.read(userPreferencesProvider).audioSource; - final database = ref.read(databaseProvider); - final cachedSource = await (database.select(database.sourceMatchTable) - ..where((s) => s.trackId.equals(query.id)) - ..limit(1) - ..orderBy([ - (s) => - OrderingTerm(expression: s.createdAt, mode: OrderingMode.desc), - ])) - .getSingleOrNull(); - final preferences = ref.read(userPreferencesProvider); - final pipedClient = ref.read(pipedProvider); - - if (cachedSource == null) { - final siblings = await fetchSiblings(ref: ref, query: query); - if (siblings.isEmpty) { - throw TrackNotFoundError(query); - } - - await database.into(database.sourceMatchTable).insert( - SourceMatchTableCompanion.insert( - trackId: query.id, - sourceId: siblings.first.info.id, - sourceType: Value( - preferences.searchMode == SearchMode.youtube - ? SourceType.youtube - : SourceType.youtubeMusic, - ), - ), - ); - - return PipedSourcedTrack( - ref: ref, - siblings: siblings.map((s) => s.info).skip(1).toList(), - source: audioSource, - info: siblings.first.info, - query: query, - sources: siblings.first.source!, - ); - } else { - final manifest = await pipedClient.streams(cachedSource.sourceId); - - return PipedSourcedTrack( - ref: ref, - siblings: [], - sources: toSources(manifest), - info: TrackSourceInfo( - id: manifest.id, - artists: manifest.uploader, - pageUrl: "https://www.youtube.com/watch?v=${manifest.id}", - thumbnail: manifest.thumbnailUrl, - title: manifest.title, - durationMs: manifest.duration.inMilliseconds, - ), - query: query, - source: audioSource, - ); - } - } - - static List toSources(PipedStreamResponse manifest) { - return manifest.audioStreams.map((audio) { - final isMp4 = audio.format == PipedAudioStreamFormat.m4a; - return TrackSource( - url: audio.url.toString(), - quality: switch (audio.quality) { - "high" => SourceQualities.high, - "medium" => SourceQualities.medium, - _ => SourceQualities.low, - }, - codec: isMp4 ? SourceCodecs.m4a : SourceCodecs.weba, - bitrate: audio.bitrate.toString(), - qualityLabel: - "${isMp4 ? "AAC" : "Opus"} • ${(audio.bitrate / 1000).floor()}kbps " - "• ${isMp4 ? "m4a" : "weba"} • Stereo", - ); - }).toList(); - } - - static Future toSiblingType( - int index, - YoutubeVideoInfo item, - PipedClient pipedClient, - ) async { - List? sources; - if (index == 0) { - final manifest = await pipedClient.streams(item.id); - sources = toSources(manifest); - } - - final SiblingType sibling = ( - info: TrackSourceInfo( - id: item.id, - artists: item.channelName, - pageUrl: "https://www.youtube.com/watch?v=${item.id}", - thumbnail: item.thumbnailUrl, - title: item.title, - durationMs: item.duration.inMilliseconds, - ), - source: sources, - ); - - return sibling; - } - - static Future> fetchSiblings({ - required TrackSourceQuery query, - required Ref ref, - }) async { - final pipedClient = ref.read(pipedProvider); - final preference = ref.read(userPreferencesProvider); - - final searchQuery = SourcedTrack.getSearchTerm(query); - - final PipedSearchResult(items: searchResults) = await pipedClient.search( - searchQuery, - preference.searchMode == SearchMode.youtube - ? PipedFilter.videos - : PipedFilter.musicSongs, - ); - - // when falling back to piped API make sure to use the YouTube mode - final isYouTubeMusic = preference.audioSource != AudioSource.piped - ? false - : preference.searchMode == SearchMode.youtubeMusic; - - if (isYouTubeMusic) { - final artists = query.artists; - - return await Future.wait( - searchResults - .map( - (result) => YoutubeVideoInfo.fromSearchItemStream( - result as PipedSearchItemStream, - preference.searchMode, - ), - ) - .sorted((a, b) => b.views.compareTo(a.views)) - .where( - (item) => artists.any( - (artist) => - artist.toLowerCase() == item.channelName.toLowerCase(), - ), - ) - .mapIndexed((i, r) => toSiblingType(i, r, pipedClient)), - ); - } - - if (ServiceUtils.onlyContainsEnglish(searchQuery)) { - return await Future.wait( - searchResults - .whereType() - .map( - (result) => YoutubeVideoInfo.fromSearchItemStream( - result, - preference.searchMode, - ), - ) - .mapIndexed((i, r) => toSiblingType(i, r, pipedClient)), - ); - } - - final rankedSiblings = YoutubeSourcedTrack.rankResults( - searchResults - .map( - (result) => YoutubeVideoInfo.fromSearchItemStream( - result as PipedSearchItemStream, - preference.searchMode, - ), - ) - .toList(), - query, - ); - - return await Future.wait( - rankedSiblings.mapIndexed((i, r) => toSiblingType(i, r, pipedClient)), - ); - } - - @override - Future copyWithSibling() async { - if (siblings.isNotEmpty) { - return this; - } - final fetchedSiblings = await fetchSiblings(ref: ref, query: query); - - return PipedSourcedTrack( - ref: ref, - siblings: fetchedSiblings - .where((s) => s.info.id != info.id) - .map((s) => s.info) - .toList(), - source: source, - info: info, - query: query, - sources: sources, - ); - } - - @override - Future swapWithSibling(TrackSourceInfo sibling) async { - if (sibling.id == info.id) { - return null; - } - - // a sibling source that was fetched from the search results - final isStepSibling = siblings.none((s) => s.id == sibling.id); - - final newSourceInfo = isStepSibling - ? sibling - : siblings.firstWhere((s) => s.id == sibling.id); - final newSiblings = siblings.where((s) => s.id != sibling.id).toList() - ..insert(0, info); - - final pipedClient = ref.read(pipedProvider); - - final manifest = await pipedClient.streams(newSourceInfo.id); - - final database = ref.read(databaseProvider); - await database.into(database.sourceMatchTable).insert( - SourceMatchTableCompanion.insert( - trackId: query.id, - sourceId: newSourceInfo.id, - sourceType: const Value(SourceType.youtube), - // Because we're sorting by createdAt in the query - // we have to update it to indicate priority - createdAt: Value(DateTime.now()), - ), - mode: InsertMode.replace, - ); - - return PipedSourcedTrack( - ref: ref, - siblings: newSiblings, - sources: toSources(manifest), - info: newSourceInfo, - query: query, - source: source, - ); - } - - @override - Future refreshStream() async { - final manifest = await ref.read(pipedProvider).streams(info.id); - return PipedSourcedTrack( - ref: ref, - siblings: siblings, - info: info, - source: source, - query: query, - sources: toSources(manifest), - ); - } -} diff --git a/lib/services/sourced_track/sources/youtube.dart b/lib/services/sourced_track/sources/youtube.dart deleted file mode 100644 index e3e9dd39..00000000 --- a/lib/services/sourced_track/sources/youtube.dart +++ /dev/null @@ -1,439 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:dio/dio.dart'; -import 'package:drift/drift.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotube/models/database/database.dart'; -import 'package:spotube/models/playback/track_sources.dart'; -import 'package:spotube/provider/database/database.dart'; -import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; -import 'package:spotube/provider/youtube_engine/youtube_engine.dart'; -import 'package:spotube/services/dio/dio.dart'; -import 'package:spotube/services/logger/logger.dart'; -import 'package:spotube/services/song_link/song_link.dart'; -import 'package:spotube/services/sourced_track/enums.dart'; -import 'package:spotube/services/sourced_track/exceptions.dart'; -import 'package:spotube/services/sourced_track/models/video_info.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; -import 'package:spotube/utils/service_utils.dart'; -import 'package:youtube_explode_dart/youtube_explode_dart.dart'; - -final officialMusicRegex = RegExp( - r"official\s(video|audio|music\svideo|lyric\svideo|visualizer)", - caseSensitive: false, -); - -class YoutubeSourcedTrack extends SourcedTrack { - YoutubeSourcedTrack({ - required super.source, - required super.siblings, - required super.info, - required super.query, - required super.sources, - required super.ref, - }); - - static Future fetchFromTrack({ - required TrackSourceQuery query, - required Ref ref, - }) async { - final audioSource = ref.read(userPreferencesProvider).audioSource; - final database = ref.read(databaseProvider); - final cachedSource = await (database.select(database.sourceMatchTable) - ..where((s) => s.trackId.equals(query.id)) - ..limit(1) - ..orderBy([ - (s) => - OrderingTerm(expression: s.createdAt, mode: OrderingMode.desc), - ])) - .get() - .then((s) => s.firstOrNull); - - if (cachedSource == null || cachedSource.sourceType != SourceType.youtube) { - final siblings = await fetchSiblings(ref: ref, query: query); - if (siblings.isEmpty) { - throw TrackNotFoundError(query); - } - - await database.into(database.sourceMatchTable).insert( - SourceMatchTableCompanion.insert( - trackId: query.id, - sourceId: siblings.first.info.id, - sourceType: const Value(SourceType.youtube), - ), - ); - - return YoutubeSourcedTrack( - ref: ref, - siblings: siblings.map((s) => s.info).skip(1).toList(), - info: siblings.first.info, - source: audioSource, - sources: siblings.first.source ?? [], - query: query, - ); - } - final (item, manifest) = await ref - .read(youtubeEngineProvider) - .getVideoWithStreamInfo(cachedSource.sourceId); - - final sourcedTrack = YoutubeSourcedTrack( - ref: ref, - siblings: [], - sources: toTrackSources(manifest), - info: TrackSourceInfo( - id: item.id.value, - artists: item.author, - pageUrl: item.url, - thumbnail: item.thumbnails.highResUrl, - title: item.title, - durationMs: item.duration?.inMilliseconds ?? 0, - ), - query: query, - source: audioSource, - ); - - AppLogger.log.i("${query.title}: ${sourcedTrack.url}"); - - return sourcedTrack; - } - - static List toTrackSources(StreamManifest manifest) { - return manifest.audioOnly.map((streamInfo) { - var isWebm = streamInfo.codec.mimeType == "audio/webm"; - return TrackSource( - url: streamInfo.url.toString(), - quality: switch (streamInfo.qualityLabel) { - "medium" => SourceQualities.medium, - "high" => SourceQualities.high, - "low" => SourceQualities.low, - _ => SourceQualities.high, - }, - codec: isWebm ? SourceCodecs.weba : SourceCodecs.m4a, - bitrate: streamInfo.bitrate.bitsPerSecond.toString(), - qualityLabel: - "${isWebm ? "Opus" : "AAC"} • ${(streamInfo.bitrate.kiloBitsPerSecond).floor()}kbps " - "• ${isWebm ? "weba" : "m4a"} • Stereo", - ); - }).toList(); - } - - static Future toSiblingType( - int index, - YoutubeVideoInfo item, - dynamic ref, - ) async { - assert(ref is WidgetRef || ref is Ref, "Invalid ref type"); - - List? sourceMap; - if (index == 0) { - final manifest = - await ref.read(youtubeEngineProvider).getStreamManifest(item.id); - sourceMap = toTrackSources(manifest); - } - - final SiblingType sibling = ( - info: TrackSourceInfo( - id: item.id, - artists: item.channelName, - pageUrl: "https://www.youtube.com/watch?v=${item.id}", - thumbnail: item.thumbnailUrl, - title: item.title, - durationMs: item.duration.inMilliseconds, - ), - source: sourceMap, - ); - - return sibling; - } - - static List rankResults( - List results, TrackSourceQuery track) { - return results - .sorted((a, b) => b.views.compareTo(a.views)) - .map((sibling) { - int score = 0; - - for (final artist in track.artists) { - final isSameChannelArtist = - sibling.channelName.toLowerCase() == artist.toLowerCase(); - final channelContainsArtist = sibling.channelName - .toLowerCase() - .contains(artist.toLowerCase()); - - if (isSameChannelArtist || channelContainsArtist) { - score += 1; - } - - final titleContainsArtist = - sibling.title.toLowerCase().contains(artist.toLowerCase()); - - if (titleContainsArtist) { - score += 1; - } - } - - final titleContainsTrackName = - sibling.title.toLowerCase().contains(track.title.toLowerCase()); - - final hasOfficialFlag = - officialMusicRegex.hasMatch(sibling.title.toLowerCase()); - - if (titleContainsTrackName) { - score += 3; - } - - if (hasOfficialFlag) { - score += 1; - } - - if (hasOfficialFlag && titleContainsTrackName) { - score += 2; - } - - return (sibling: sibling, score: score); - }) - .sorted((a, b) => b.score.compareTo(a.score)) - .map((e) => e.sibling) - .toList(); - } - - static Future> fetchFromIsrc({ - required TrackSourceQuery track, - required Ref ref, - }) async { - final isrcResults = []; - final isrc = track.isrc; - if (isrc.isNotEmpty) { - final searchedVideos = - await ref.read(youtubeEngineProvider).searchVideos(isrc.toString()); - if (searchedVideos.isNotEmpty) { - AppLogger.log - .d("${track.title} ISRC $isrc Total ${searchedVideos.length}"); - - final stringBuffer = StringBuffer(); - - final filteredMatches = searchedVideos - .map(YoutubeVideoInfo.fromVideo) - .map((YoutubeVideoInfo videoInfo) { - final ytWords = videoInfo.title - .toLowerCase() - .replaceAll(RegExp(r'[^\p{L}\p{N}\p{Z}]+', unicode: true), '') - .split(RegExp(r'\p{Z}+', unicode: true)) - .where((item) => item.isNotEmpty); - final spWords = track.title - .toLowerCase() - .replaceAll(RegExp(r'[^\p{L}\p{N}\p{Z}]+', unicode: true), '') - .split(RegExp(r'\p{Z}+', unicode: true)) - .where((item) => item.isNotEmpty); - // Single word and duration match with 3 second tolerance - if (ytWords.any((word) => spWords.contains(word)) && - (videoInfo.duration - - Duration(milliseconds: track.durationMs)) - .abs() - .inMilliseconds <= - 3000) { - stringBuffer.writeln( - "ISRC MATCH: ${videoInfo.id} ${videoInfo.title} by ${videoInfo.channelName} ${videoInfo.duration}", - ); - - return videoInfo; - } - return null; - }) - .nonNulls - .toList(); - - AppLogger.log.d(stringBuffer.toString()); - - isrcResults.addAll(filteredMatches); - } - } - return isrcResults; - } - - static Future> fetchSiblings({ - required TrackSourceQuery query, - required Ref ref, - }) async { - final videoResults = []; - - if (query is! SourcedTrack) { - final isrcResults = await fetchFromIsrc( - track: query, - ref: ref, - ); - - videoResults.addAll(isrcResults); - - if (isrcResults.isEmpty) { - AppLogger.log.w("No ISRC results found, falling back to SongLink"); - - final links = await SongLinkService.links(query.id); - - final stringBuffer = links.fold( - StringBuffer(), - (previousValue, element) { - previousValue.writeln( - "SongLink ${query.id} ${element.platform} ${element.url}"); - return previousValue; - }, - ); - - AppLogger.log.d(stringBuffer.toString()); - - final ytLink = links.firstWhereOrNull( - (link) => link.platform == "youtube", - ); - if (ytLink?.url != null) { - try { - videoResults.add( - YoutubeVideoInfo.fromVideo(await ref - .read(youtubeEngineProvider) - .getVideo(Uri.parse(ytLink!.url!).queryParameters["v"]!)), - ); - } on VideoUnplayableException catch (e, stack) { - // Ignore this error and continue with the search - AppLogger.reportError(e, stack); - } - } else { - AppLogger.log.w("No YouTube link found in SongLink results"); - } - } - } - - final searchQuery = SourcedTrack.getSearchTerm(query); - - final searchResults = - await ref.read(youtubeEngineProvider).searchVideos(searchQuery); - - if (ServiceUtils.onlyContainsEnglish(searchQuery)) { - videoResults - .addAll(searchResults.map(YoutubeVideoInfo.fromVideo).toList()); - } else { - videoResults.addAll(rankResults( - searchResults.map(YoutubeVideoInfo.fromVideo).toList(), - query, - )); - } - - final seenIds = {}; - int index = 0; - return await Future.wait( - videoResults.map((videoResult) async { - // Deduplicate results - if (!seenIds.contains(videoResult.id)) { - seenIds.add(videoResult.id); - return await toSiblingType(index++, videoResult, ref); - } - return null; - }), - ).then((s) => s.whereType().toList()); - } - - @override - Future swapWithSibling(TrackSourceInfo sibling) async { - if (sibling.id == info.id) { - return null; - } - - // a sibling source that was fetched from the search results - final isStepSibling = siblings.none((s) => s.id == sibling.id); - - final newSourceInfo = isStepSibling - ? sibling - : siblings.firstWhere((s) => s.id == sibling.id); - - final newSiblings = siblings.where((s) => s.id != sibling.id).toList() - ..insert(0, info); - - final manifest = await ref - .read(youtubeEngineProvider) - .getStreamManifest(newSourceInfo.id); - - final database = ref.read(databaseProvider); - - await database.into(database.sourceMatchTable).insert( - SourceMatchTableCompanion.insert( - trackId: query.id, - sourceId: newSourceInfo.id, - sourceType: const Value(SourceType.youtube), - // Because we're sorting by createdAt in the query - // we have to update it to indicate priority - createdAt: Value(DateTime.now()), - ), - mode: InsertMode.replace, - ); - - return YoutubeSourcedTrack( - ref: ref, - source: source, - siblings: newSiblings, - sources: toTrackSources(manifest), - info: newSourceInfo, - query: query, - ); - } - - @override - Future copyWithSibling() async { - if (siblings.isNotEmpty) { - return this; - } - final fetchedSiblings = await fetchSiblings(ref: ref, query: query); - - return YoutubeSourcedTrack( - ref: ref, - siblings: fetchedSiblings - .where((s) => s.info.id != info.id) - .map((s) => s.info) - .toList(), - source: source, - sources: sources, - info: info, - query: query, - ); - } - - @override - Future refreshStream() async { - List validStreams = []; - - final stringBuffer = StringBuffer(); - for (final source in sources) { - final res = await globalDio.head( - source.url, - options: - Options(validateStatus: (status) => status != null && status < 500), - ); - - stringBuffer.writeln( - "[${query.id}] ${res.statusCode} ${source.quality} ${source.codec} ${source.bitrate}", - ); - - if (res.statusCode! < 400) { - validStreams.add(source); - } - } - - AppLogger.log.d(stringBuffer.toString()); - - if (validStreams.isEmpty) { - final manifest = - await ref.read(youtubeEngineProvider).getStreamManifest(info.id); - - validStreams = toTrackSources(manifest); - } - - final sourcedTrack = YoutubeSourcedTrack( - ref: ref, - siblings: siblings, - source: source, - sources: validStreams, - info: info, - query: query, - ); - - AppLogger.log.i("Refreshing ${query.title}: ${sourcedTrack.url}"); - - return sourcedTrack; - } -} diff --git a/lib/services/youtube_engine/youtube_explode_engine.dart b/lib/services/youtube_engine/youtube_explode_engine.dart index fa58314c..c552f883 100644 --- a/lib/services/youtube_engine/youtube_explode_engine.dart +++ b/lib/services/youtube_engine/youtube_explode_engine.dart @@ -162,7 +162,6 @@ class YouTubeExplodeEngine implements YouTubeEngine { requireWatchPage: false, ytClients: [ YoutubeApiClient.ios, - YoutubeApiClient.android, YoutubeApiClient.androidVr, ], ); diff --git a/pubspec.lock b/pubspec.lock index 08757d74..8623af4e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2888,12 +2888,11 @@ packages: youtube_explode_dart: dependency: "direct main" description: - path: "." - ref: HEAD - resolved-ref: caa3023386dbc10e69c99f49f491148094874671 - url: "https://github.com/Coronon/youtube_explode_dart" - source: git - version: "2.5.2" + name: youtube_explode_dart + sha256: "947ba05e0c4f050743e480e7bca3575ff6427d86cc898c1a69f5e1d188cdc9e0" + url: "https://pub.dev" + source: hosted + version: "2.5.3" yt_dlp_dart: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 46273a32..4087bc0d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -138,8 +138,7 @@ dependencies: wikipedia_api: ^0.1.0 win32_registry: ^1.1.5 window_manager: ^0.4.3 - youtube_explode_dart: - git: https://github.com/Coronon/youtube_explode_dart + youtube_explode_dart: ^2.5.3 yt_dlp_dart: git: url: https://github.com/KRTirtho/yt_dlp_dart.git diff --git a/test/drift/app_db/generated/schema.dart b/test/drift/app_db/generated/schema.dart index 413b4408..76573e49 100644 --- a/test/drift/app_db/generated/schema.dart +++ b/test/drift/app_db/generated/schema.dart @@ -12,6 +12,7 @@ import 'schema_v6.dart' as v6; import 'schema_v7.dart' as v7; import 'schema_v8.dart' as v8; import 'schema_v9.dart' as v9; +import 'schema_v10.dart' as v10; class GeneratedHelper implements SchemaInstantiationHelper { @override @@ -35,10 +36,12 @@ class GeneratedHelper implements SchemaInstantiationHelper { return v8.DatabaseAtV8(db); case 9: return v9.DatabaseAtV9(db); + case 10: + return v10.DatabaseAtV10(db); default: throw MissingSchemaException(version, versions); } } - static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9]; + static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; } diff --git a/test/drift/app_db/generated/schema_v10.dart b/test/drift/app_db/generated/schema_v10.dart new file mode 100644 index 00000000..36cc2a6b --- /dev/null +++ b/test/drift/app_db/generated/schema_v10.dart @@ -0,0 +1,3472 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class AuthenticationTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthenticationTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn cookie = GeneratedColumn( + 'cookie', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn accessToken = GeneratedColumn( + 'access_token', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn expiration = GeneratedColumn( + 'expiration', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + @override + List get $columns => [id, cookie, accessToken, expiration]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'authentication_table'; + @override + Set get $primaryKey => {id}; + @override + AuthenticationTableData map(Map data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthenticationTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + cookie: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}cookie'])!, + accessToken: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}access_token'])!, + expiration: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}expiration'])!, + ); + } + + @override + AuthenticationTable createAlias(String alias) { + return AuthenticationTable(attachedDatabase, alias); + } +} + +class AuthenticationTableData extends DataClass + implements Insertable { + final int id; + final String cookie; + final String accessToken; + final DateTime expiration; + const AuthenticationTableData( + {required this.id, + required this.cookie, + required this.accessToken, + required this.expiration}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['cookie'] = Variable(cookie); + map['access_token'] = Variable(accessToken); + map['expiration'] = Variable(expiration); + return map; + } + + AuthenticationTableCompanion toCompanion(bool nullToAbsent) { + return AuthenticationTableCompanion( + id: Value(id), + cookie: Value(cookie), + accessToken: Value(accessToken), + expiration: Value(expiration), + ); + } + + factory AuthenticationTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthenticationTableData( + id: serializer.fromJson(json['id']), + cookie: serializer.fromJson(json['cookie']), + accessToken: serializer.fromJson(json['accessToken']), + expiration: serializer.fromJson(json['expiration']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'cookie': serializer.toJson(cookie), + 'accessToken': serializer.toJson(accessToken), + 'expiration': serializer.toJson(expiration), + }; + } + + AuthenticationTableData copyWith( + {int? id, + String? cookie, + String? accessToken, + DateTime? expiration}) => + AuthenticationTableData( + id: id ?? this.id, + cookie: cookie ?? this.cookie, + accessToken: accessToken ?? this.accessToken, + expiration: expiration ?? this.expiration, + ); + AuthenticationTableData copyWithCompanion(AuthenticationTableCompanion data) { + return AuthenticationTableData( + id: data.id.present ? data.id.value : this.id, + cookie: data.cookie.present ? data.cookie.value : this.cookie, + accessToken: + data.accessToken.present ? data.accessToken.value : this.accessToken, + expiration: + data.expiration.present ? data.expiration.value : this.expiration, + ); + } + + @override + String toString() { + return (StringBuffer('AuthenticationTableData(') + ..write('id: $id, ') + ..write('cookie: $cookie, ') + ..write('accessToken: $accessToken, ') + ..write('expiration: $expiration') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, cookie, accessToken, expiration); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthenticationTableData && + other.id == this.id && + other.cookie == this.cookie && + other.accessToken == this.accessToken && + other.expiration == this.expiration); +} + +class AuthenticationTableCompanion + extends UpdateCompanion { + final Value id; + final Value cookie; + final Value accessToken; + final Value expiration; + const AuthenticationTableCompanion({ + this.id = const Value.absent(), + this.cookie = const Value.absent(), + this.accessToken = const Value.absent(), + this.expiration = const Value.absent(), + }); + AuthenticationTableCompanion.insert({ + this.id = const Value.absent(), + required String cookie, + required String accessToken, + required DateTime expiration, + }) : cookie = Value(cookie), + accessToken = Value(accessToken), + expiration = Value(expiration); + static Insertable custom({ + Expression? id, + Expression? cookie, + Expression? accessToken, + Expression? expiration, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (cookie != null) 'cookie': cookie, + if (accessToken != null) 'access_token': accessToken, + if (expiration != null) 'expiration': expiration, + }); + } + + AuthenticationTableCompanion copyWith( + {Value? id, + Value? cookie, + Value? accessToken, + Value? expiration}) { + return AuthenticationTableCompanion( + id: id ?? this.id, + cookie: cookie ?? this.cookie, + accessToken: accessToken ?? this.accessToken, + expiration: expiration ?? this.expiration, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (cookie.present) { + map['cookie'] = Variable(cookie.value); + } + if (accessToken.present) { + map['access_token'] = Variable(accessToken.value); + } + if (expiration.present) { + map['expiration'] = Variable(expiration.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthenticationTableCompanion(') + ..write('id: $id, ') + ..write('cookie: $cookie, ') + ..write('accessToken: $accessToken, ') + ..write('expiration: $expiration') + ..write(')')) + .toString(); + } +} + +class BlacklistTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + BlacklistTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn elementType = GeneratedColumn( + 'element_type', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn elementId = GeneratedColumn( + 'element_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + @override + List get $columns => [id, name, elementType, elementId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'blacklist_table'; + @override + Set get $primaryKey => {id}; + @override + BlacklistTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return BlacklistTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name'])!, + elementType: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}element_type'])!, + elementId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}element_id'])!, + ); + } + + @override + BlacklistTable createAlias(String alias) { + return BlacklistTable(attachedDatabase, alias); + } +} + +class BlacklistTableData extends DataClass + implements Insertable { + final int id; + final String name; + final String elementType; + final String elementId; + const BlacklistTableData( + {required this.id, + required this.name, + required this.elementType, + required this.elementId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['element_type'] = Variable(elementType); + map['element_id'] = Variable(elementId); + return map; + } + + BlacklistTableCompanion toCompanion(bool nullToAbsent) { + return BlacklistTableCompanion( + id: Value(id), + name: Value(name), + elementType: Value(elementType), + elementId: Value(elementId), + ); + } + + factory BlacklistTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return BlacklistTableData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + elementType: serializer.fromJson(json['elementType']), + elementId: serializer.fromJson(json['elementId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'elementType': serializer.toJson(elementType), + 'elementId': serializer.toJson(elementId), + }; + } + + BlacklistTableData copyWith( + {int? id, String? name, String? elementType, String? elementId}) => + BlacklistTableData( + id: id ?? this.id, + name: name ?? this.name, + elementType: elementType ?? this.elementType, + elementId: elementId ?? this.elementId, + ); + BlacklistTableData copyWithCompanion(BlacklistTableCompanion data) { + return BlacklistTableData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + elementType: + data.elementType.present ? data.elementType.value : this.elementType, + elementId: data.elementId.present ? data.elementId.value : this.elementId, + ); + } + + @override + String toString() { + return (StringBuffer('BlacklistTableData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('elementType: $elementType, ') + ..write('elementId: $elementId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name, elementType, elementId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is BlacklistTableData && + other.id == this.id && + other.name == this.name && + other.elementType == this.elementType && + other.elementId == this.elementId); +} + +class BlacklistTableCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value elementType; + final Value elementId; + const BlacklistTableCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.elementType = const Value.absent(), + this.elementId = const Value.absent(), + }); + BlacklistTableCompanion.insert({ + this.id = const Value.absent(), + required String name, + required String elementType, + required String elementId, + }) : name = Value(name), + elementType = Value(elementType), + elementId = Value(elementId); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? elementType, + Expression? elementId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (elementType != null) 'element_type': elementType, + if (elementId != null) 'element_id': elementId, + }); + } + + BlacklistTableCompanion copyWith( + {Value? id, + Value? name, + Value? elementType, + Value? elementId}) { + return BlacklistTableCompanion( + id: id ?? this.id, + name: name ?? this.name, + elementType: elementType ?? this.elementType, + elementId: elementId ?? this.elementId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (elementType.present) { + map['element_type'] = Variable(elementType.value); + } + if (elementId.present) { + map['element_id'] = Variable(elementId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('BlacklistTableCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('elementType: $elementType, ') + ..write('elementId: $elementId') + ..write(')')) + .toString(); + } +} + +class PreferencesTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PreferencesTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn albumColorSync = GeneratedColumn( + 'album_color_sync', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("album_color_sync" IN (0, 1))'), + defaultValue: const Constant(true)); + late final GeneratedColumn amoledDarkTheme = GeneratedColumn( + 'amoled_dark_theme', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("amoled_dark_theme" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn checkUpdate = GeneratedColumn( + 'check_update', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("check_update" IN (0, 1))'), + defaultValue: const Constant(true)); + late final GeneratedColumn normalizeAudio = GeneratedColumn( + 'normalize_audio', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("normalize_audio" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn showSystemTrayIcon = GeneratedColumn( + 'show_system_tray_icon', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("show_system_tray_icon" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn systemTitleBar = GeneratedColumn( + 'system_title_bar', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("system_title_bar" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn skipNonMusic = GeneratedColumn( + 'skip_non_music', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("skip_non_music" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn closeBehavior = GeneratedColumn( + 'close_behavior', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(CloseBehavior.close.name)); + late final GeneratedColumn accentColorScheme = + GeneratedColumn('accent_color_scheme', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("Slate:0xff64748b")); + late final GeneratedColumn layoutMode = GeneratedColumn( + 'layout_mode', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(LayoutMode.adaptive.name)); + late final GeneratedColumn locale = GeneratedColumn( + 'locale', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: + const Constant('{"languageCode":"system","countryCode":"system"}')); + late final GeneratedColumn market = GeneratedColumn( + 'market', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(Market.US.name)); + late final GeneratedColumn searchMode = GeneratedColumn( + 'search_mode', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(SearchMode.youtube.name)); + late final GeneratedColumn downloadLocation = GeneratedColumn( + 'download_location', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("")); + late final GeneratedColumn localLibraryLocation = + GeneratedColumn('local_library_location', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("")); + late final GeneratedColumn pipedInstance = GeneratedColumn( + 'piped_instance', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("https://pipedapi.kavin.rocks")); + late final GeneratedColumn invidiousInstance = + GeneratedColumn('invidious_instance', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("https://inv.nadeko.net")); + late final GeneratedColumn themeMode = GeneratedColumn( + 'theme_mode', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(ThemeMode.system.name)); + late final GeneratedColumn audioSourceId = GeneratedColumn( + 'audio_source_id', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn youtubeClientEngine = + GeneratedColumn('youtube_client_engine', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(YoutubeClientEngine.youtubeExplode.name)); + late final GeneratedColumn discordPresence = GeneratedColumn( + 'discord_presence', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("discord_presence" IN (0, 1))'), + defaultValue: const Constant(true)); + late final GeneratedColumn endlessPlayback = GeneratedColumn( + 'endless_playback', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("endless_playback" IN (0, 1))'), + defaultValue: const Constant(true)); + late final GeneratedColumn enableConnect = GeneratedColumn( + 'enable_connect', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("enable_connect" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn connectPort = GeneratedColumn( + 'connect_port', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(-1)); + late final GeneratedColumn cacheMusic = GeneratedColumn( + 'cache_music', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("cache_music" IN (0, 1))'), + defaultValue: const Constant(true)); + @override + List get $columns => [ + id, + albumColorSync, + amoledDarkTheme, + checkUpdate, + normalizeAudio, + showSystemTrayIcon, + systemTitleBar, + skipNonMusic, + closeBehavior, + accentColorScheme, + layoutMode, + locale, + market, + searchMode, + downloadLocation, + localLibraryLocation, + pipedInstance, + invidiousInstance, + themeMode, + audioSourceId, + youtubeClientEngine, + discordPresence, + endlessPlayback, + enableConnect, + connectPort, + cacheMusic + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'preferences_table'; + @override + Set get $primaryKey => {id}; + @override + PreferencesTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PreferencesTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + albumColorSync: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}album_color_sync'])!, + amoledDarkTheme: attachedDatabase.typeMapping.read( + DriftSqlType.bool, data['${effectivePrefix}amoled_dark_theme'])!, + checkUpdate: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}check_update'])!, + normalizeAudio: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}normalize_audio'])!, + showSystemTrayIcon: attachedDatabase.typeMapping.read( + DriftSqlType.bool, data['${effectivePrefix}show_system_tray_icon'])!, + systemTitleBar: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}system_title_bar'])!, + skipNonMusic: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}skip_non_music'])!, + closeBehavior: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}close_behavior'])!, + accentColorScheme: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}accent_color_scheme'])!, + layoutMode: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}layout_mode'])!, + locale: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}locale'])!, + market: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}market'])!, + searchMode: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}search_mode'])!, + downloadLocation: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}download_location'])!, + localLibraryLocation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}local_library_location'])!, + pipedInstance: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}piped_instance'])!, + invidiousInstance: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}invidious_instance'])!, + themeMode: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}theme_mode'])!, + audioSourceId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}audio_source_id']), + youtubeClientEngine: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}youtube_client_engine'])!, + discordPresence: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}discord_presence'])!, + endlessPlayback: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}endless_playback'])!, + enableConnect: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}enable_connect'])!, + connectPort: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}connect_port'])!, + cacheMusic: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}cache_music'])!, + ); + } + + @override + PreferencesTable createAlias(String alias) { + return PreferencesTable(attachedDatabase, alias); + } +} + +class PreferencesTableData extends DataClass + implements Insertable { + final int id; + final bool albumColorSync; + final bool amoledDarkTheme; + final bool checkUpdate; + final bool normalizeAudio; + final bool showSystemTrayIcon; + final bool systemTitleBar; + final bool skipNonMusic; + final String closeBehavior; + final String accentColorScheme; + final String layoutMode; + final String locale; + final String market; + final String searchMode; + final String downloadLocation; + final String localLibraryLocation; + final String pipedInstance; + final String invidiousInstance; + final String themeMode; + final String? audioSourceId; + final String youtubeClientEngine; + final bool discordPresence; + final bool endlessPlayback; + final bool enableConnect; + final int connectPort; + final bool cacheMusic; + const PreferencesTableData( + {required this.id, + required this.albumColorSync, + required this.amoledDarkTheme, + required this.checkUpdate, + required this.normalizeAudio, + required this.showSystemTrayIcon, + required this.systemTitleBar, + required this.skipNonMusic, + required this.closeBehavior, + required this.accentColorScheme, + required this.layoutMode, + required this.locale, + required this.market, + required this.searchMode, + required this.downloadLocation, + required this.localLibraryLocation, + required this.pipedInstance, + required this.invidiousInstance, + required this.themeMode, + this.audioSourceId, + required this.youtubeClientEngine, + required this.discordPresence, + required this.endlessPlayback, + required this.enableConnect, + required this.connectPort, + required this.cacheMusic}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['album_color_sync'] = Variable(albumColorSync); + map['amoled_dark_theme'] = Variable(amoledDarkTheme); + map['check_update'] = Variable(checkUpdate); + map['normalize_audio'] = Variable(normalizeAudio); + map['show_system_tray_icon'] = Variable(showSystemTrayIcon); + map['system_title_bar'] = Variable(systemTitleBar); + map['skip_non_music'] = Variable(skipNonMusic); + map['close_behavior'] = Variable(closeBehavior); + map['accent_color_scheme'] = Variable(accentColorScheme); + map['layout_mode'] = Variable(layoutMode); + map['locale'] = Variable(locale); + map['market'] = Variable(market); + map['search_mode'] = Variable(searchMode); + map['download_location'] = Variable(downloadLocation); + map['local_library_location'] = Variable(localLibraryLocation); + map['piped_instance'] = Variable(pipedInstance); + map['invidious_instance'] = Variable(invidiousInstance); + map['theme_mode'] = Variable(themeMode); + if (!nullToAbsent || audioSourceId != null) { + map['audio_source_id'] = Variable(audioSourceId); + } + map['youtube_client_engine'] = Variable(youtubeClientEngine); + map['discord_presence'] = Variable(discordPresence); + map['endless_playback'] = Variable(endlessPlayback); + map['enable_connect'] = Variable(enableConnect); + map['connect_port'] = Variable(connectPort); + map['cache_music'] = Variable(cacheMusic); + return map; + } + + PreferencesTableCompanion toCompanion(bool nullToAbsent) { + return PreferencesTableCompanion( + id: Value(id), + albumColorSync: Value(albumColorSync), + amoledDarkTheme: Value(amoledDarkTheme), + checkUpdate: Value(checkUpdate), + normalizeAudio: Value(normalizeAudio), + showSystemTrayIcon: Value(showSystemTrayIcon), + systemTitleBar: Value(systemTitleBar), + skipNonMusic: Value(skipNonMusic), + closeBehavior: Value(closeBehavior), + accentColorScheme: Value(accentColorScheme), + layoutMode: Value(layoutMode), + locale: Value(locale), + market: Value(market), + searchMode: Value(searchMode), + downloadLocation: Value(downloadLocation), + localLibraryLocation: Value(localLibraryLocation), + pipedInstance: Value(pipedInstance), + invidiousInstance: Value(invidiousInstance), + themeMode: Value(themeMode), + audioSourceId: audioSourceId == null && nullToAbsent + ? const Value.absent() + : Value(audioSourceId), + youtubeClientEngine: Value(youtubeClientEngine), + discordPresence: Value(discordPresence), + endlessPlayback: Value(endlessPlayback), + enableConnect: Value(enableConnect), + connectPort: Value(connectPort), + cacheMusic: Value(cacheMusic), + ); + } + + factory PreferencesTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PreferencesTableData( + id: serializer.fromJson(json['id']), + albumColorSync: serializer.fromJson(json['albumColorSync']), + amoledDarkTheme: serializer.fromJson(json['amoledDarkTheme']), + checkUpdate: serializer.fromJson(json['checkUpdate']), + normalizeAudio: serializer.fromJson(json['normalizeAudio']), + showSystemTrayIcon: serializer.fromJson(json['showSystemTrayIcon']), + systemTitleBar: serializer.fromJson(json['systemTitleBar']), + skipNonMusic: serializer.fromJson(json['skipNonMusic']), + closeBehavior: serializer.fromJson(json['closeBehavior']), + accentColorScheme: serializer.fromJson(json['accentColorScheme']), + layoutMode: serializer.fromJson(json['layoutMode']), + locale: serializer.fromJson(json['locale']), + market: serializer.fromJson(json['market']), + searchMode: serializer.fromJson(json['searchMode']), + downloadLocation: serializer.fromJson(json['downloadLocation']), + localLibraryLocation: + serializer.fromJson(json['localLibraryLocation']), + pipedInstance: serializer.fromJson(json['pipedInstance']), + invidiousInstance: serializer.fromJson(json['invidiousInstance']), + themeMode: serializer.fromJson(json['themeMode']), + audioSourceId: serializer.fromJson(json['audioSourceId']), + youtubeClientEngine: + serializer.fromJson(json['youtubeClientEngine']), + discordPresence: serializer.fromJson(json['discordPresence']), + endlessPlayback: serializer.fromJson(json['endlessPlayback']), + enableConnect: serializer.fromJson(json['enableConnect']), + connectPort: serializer.fromJson(json['connectPort']), + cacheMusic: serializer.fromJson(json['cacheMusic']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'albumColorSync': serializer.toJson(albumColorSync), + 'amoledDarkTheme': serializer.toJson(amoledDarkTheme), + 'checkUpdate': serializer.toJson(checkUpdate), + 'normalizeAudio': serializer.toJson(normalizeAudio), + 'showSystemTrayIcon': serializer.toJson(showSystemTrayIcon), + 'systemTitleBar': serializer.toJson(systemTitleBar), + 'skipNonMusic': serializer.toJson(skipNonMusic), + 'closeBehavior': serializer.toJson(closeBehavior), + 'accentColorScheme': serializer.toJson(accentColorScheme), + 'layoutMode': serializer.toJson(layoutMode), + 'locale': serializer.toJson(locale), + 'market': serializer.toJson(market), + 'searchMode': serializer.toJson(searchMode), + 'downloadLocation': serializer.toJson(downloadLocation), + 'localLibraryLocation': serializer.toJson(localLibraryLocation), + 'pipedInstance': serializer.toJson(pipedInstance), + 'invidiousInstance': serializer.toJson(invidiousInstance), + 'themeMode': serializer.toJson(themeMode), + 'audioSourceId': serializer.toJson(audioSourceId), + 'youtubeClientEngine': serializer.toJson(youtubeClientEngine), + 'discordPresence': serializer.toJson(discordPresence), + 'endlessPlayback': serializer.toJson(endlessPlayback), + 'enableConnect': serializer.toJson(enableConnect), + 'connectPort': serializer.toJson(connectPort), + 'cacheMusic': serializer.toJson(cacheMusic), + }; + } + + PreferencesTableData copyWith( + {int? id, + bool? albumColorSync, + bool? amoledDarkTheme, + bool? checkUpdate, + bool? normalizeAudio, + bool? showSystemTrayIcon, + bool? systemTitleBar, + bool? skipNonMusic, + String? closeBehavior, + String? accentColorScheme, + String? layoutMode, + String? locale, + String? market, + String? searchMode, + String? downloadLocation, + String? localLibraryLocation, + String? pipedInstance, + String? invidiousInstance, + String? themeMode, + Value audioSourceId = const Value.absent(), + String? youtubeClientEngine, + bool? discordPresence, + bool? endlessPlayback, + bool? enableConnect, + int? connectPort, + bool? cacheMusic}) => + PreferencesTableData( + id: id ?? this.id, + albumColorSync: albumColorSync ?? this.albumColorSync, + amoledDarkTheme: amoledDarkTheme ?? this.amoledDarkTheme, + checkUpdate: checkUpdate ?? this.checkUpdate, + normalizeAudio: normalizeAudio ?? this.normalizeAudio, + showSystemTrayIcon: showSystemTrayIcon ?? this.showSystemTrayIcon, + systemTitleBar: systemTitleBar ?? this.systemTitleBar, + skipNonMusic: skipNonMusic ?? this.skipNonMusic, + closeBehavior: closeBehavior ?? this.closeBehavior, + accentColorScheme: accentColorScheme ?? this.accentColorScheme, + layoutMode: layoutMode ?? this.layoutMode, + locale: locale ?? this.locale, + market: market ?? this.market, + searchMode: searchMode ?? this.searchMode, + downloadLocation: downloadLocation ?? this.downloadLocation, + localLibraryLocation: localLibraryLocation ?? this.localLibraryLocation, + pipedInstance: pipedInstance ?? this.pipedInstance, + invidiousInstance: invidiousInstance ?? this.invidiousInstance, + themeMode: themeMode ?? this.themeMode, + audioSourceId: + audioSourceId.present ? audioSourceId.value : this.audioSourceId, + youtubeClientEngine: youtubeClientEngine ?? this.youtubeClientEngine, + discordPresence: discordPresence ?? this.discordPresence, + endlessPlayback: endlessPlayback ?? this.endlessPlayback, + enableConnect: enableConnect ?? this.enableConnect, + connectPort: connectPort ?? this.connectPort, + cacheMusic: cacheMusic ?? this.cacheMusic, + ); + PreferencesTableData copyWithCompanion(PreferencesTableCompanion data) { + return PreferencesTableData( + id: data.id.present ? data.id.value : this.id, + albumColorSync: data.albumColorSync.present + ? data.albumColorSync.value + : this.albumColorSync, + amoledDarkTheme: data.amoledDarkTheme.present + ? data.amoledDarkTheme.value + : this.amoledDarkTheme, + checkUpdate: + data.checkUpdate.present ? data.checkUpdate.value : this.checkUpdate, + normalizeAudio: data.normalizeAudio.present + ? data.normalizeAudio.value + : this.normalizeAudio, + showSystemTrayIcon: data.showSystemTrayIcon.present + ? data.showSystemTrayIcon.value + : this.showSystemTrayIcon, + systemTitleBar: data.systemTitleBar.present + ? data.systemTitleBar.value + : this.systemTitleBar, + skipNonMusic: data.skipNonMusic.present + ? data.skipNonMusic.value + : this.skipNonMusic, + closeBehavior: data.closeBehavior.present + ? data.closeBehavior.value + : this.closeBehavior, + accentColorScheme: data.accentColorScheme.present + ? data.accentColorScheme.value + : this.accentColorScheme, + layoutMode: + data.layoutMode.present ? data.layoutMode.value : this.layoutMode, + locale: data.locale.present ? data.locale.value : this.locale, + market: data.market.present ? data.market.value : this.market, + searchMode: + data.searchMode.present ? data.searchMode.value : this.searchMode, + downloadLocation: data.downloadLocation.present + ? data.downloadLocation.value + : this.downloadLocation, + localLibraryLocation: data.localLibraryLocation.present + ? data.localLibraryLocation.value + : this.localLibraryLocation, + pipedInstance: data.pipedInstance.present + ? data.pipedInstance.value + : this.pipedInstance, + invidiousInstance: data.invidiousInstance.present + ? data.invidiousInstance.value + : this.invidiousInstance, + themeMode: data.themeMode.present ? data.themeMode.value : this.themeMode, + audioSourceId: data.audioSourceId.present + ? data.audioSourceId.value + : this.audioSourceId, + youtubeClientEngine: data.youtubeClientEngine.present + ? data.youtubeClientEngine.value + : this.youtubeClientEngine, + discordPresence: data.discordPresence.present + ? data.discordPresence.value + : this.discordPresence, + endlessPlayback: data.endlessPlayback.present + ? data.endlessPlayback.value + : this.endlessPlayback, + enableConnect: data.enableConnect.present + ? data.enableConnect.value + : this.enableConnect, + connectPort: + data.connectPort.present ? data.connectPort.value : this.connectPort, + cacheMusic: + data.cacheMusic.present ? data.cacheMusic.value : this.cacheMusic, + ); + } + + @override + String toString() { + return (StringBuffer('PreferencesTableData(') + ..write('id: $id, ') + ..write('albumColorSync: $albumColorSync, ') + ..write('amoledDarkTheme: $amoledDarkTheme, ') + ..write('checkUpdate: $checkUpdate, ') + ..write('normalizeAudio: $normalizeAudio, ') + ..write('showSystemTrayIcon: $showSystemTrayIcon, ') + ..write('systemTitleBar: $systemTitleBar, ') + ..write('skipNonMusic: $skipNonMusic, ') + ..write('closeBehavior: $closeBehavior, ') + ..write('accentColorScheme: $accentColorScheme, ') + ..write('layoutMode: $layoutMode, ') + ..write('locale: $locale, ') + ..write('market: $market, ') + ..write('searchMode: $searchMode, ') + ..write('downloadLocation: $downloadLocation, ') + ..write('localLibraryLocation: $localLibraryLocation, ') + ..write('pipedInstance: $pipedInstance, ') + ..write('invidiousInstance: $invidiousInstance, ') + ..write('themeMode: $themeMode, ') + ..write('audioSourceId: $audioSourceId, ') + ..write('youtubeClientEngine: $youtubeClientEngine, ') + ..write('discordPresence: $discordPresence, ') + ..write('endlessPlayback: $endlessPlayback, ') + ..write('enableConnect: $enableConnect, ') + ..write('connectPort: $connectPort, ') + ..write('cacheMusic: $cacheMusic') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + id, + albumColorSync, + amoledDarkTheme, + checkUpdate, + normalizeAudio, + showSystemTrayIcon, + systemTitleBar, + skipNonMusic, + closeBehavior, + accentColorScheme, + layoutMode, + locale, + market, + searchMode, + downloadLocation, + localLibraryLocation, + pipedInstance, + invidiousInstance, + themeMode, + audioSourceId, + youtubeClientEngine, + discordPresence, + endlessPlayback, + enableConnect, + connectPort, + cacheMusic + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PreferencesTableData && + other.id == this.id && + other.albumColorSync == this.albumColorSync && + other.amoledDarkTheme == this.amoledDarkTheme && + other.checkUpdate == this.checkUpdate && + other.normalizeAudio == this.normalizeAudio && + other.showSystemTrayIcon == this.showSystemTrayIcon && + other.systemTitleBar == this.systemTitleBar && + other.skipNonMusic == this.skipNonMusic && + other.closeBehavior == this.closeBehavior && + other.accentColorScheme == this.accentColorScheme && + other.layoutMode == this.layoutMode && + other.locale == this.locale && + other.market == this.market && + other.searchMode == this.searchMode && + other.downloadLocation == this.downloadLocation && + other.localLibraryLocation == this.localLibraryLocation && + other.pipedInstance == this.pipedInstance && + other.invidiousInstance == this.invidiousInstance && + other.themeMode == this.themeMode && + other.audioSourceId == this.audioSourceId && + other.youtubeClientEngine == this.youtubeClientEngine && + other.discordPresence == this.discordPresence && + other.endlessPlayback == this.endlessPlayback && + other.enableConnect == this.enableConnect && + other.connectPort == this.connectPort && + other.cacheMusic == this.cacheMusic); +} + +class PreferencesTableCompanion extends UpdateCompanion { + final Value id; + final Value albumColorSync; + final Value amoledDarkTheme; + final Value checkUpdate; + final Value normalizeAudio; + final Value showSystemTrayIcon; + final Value systemTitleBar; + final Value skipNonMusic; + final Value closeBehavior; + final Value accentColorScheme; + final Value layoutMode; + final Value locale; + final Value market; + final Value searchMode; + final Value downloadLocation; + final Value localLibraryLocation; + final Value pipedInstance; + final Value invidiousInstance; + final Value themeMode; + final Value audioSourceId; + final Value youtubeClientEngine; + final Value discordPresence; + final Value endlessPlayback; + final Value enableConnect; + final Value connectPort; + final Value cacheMusic; + const PreferencesTableCompanion({ + this.id = const Value.absent(), + this.albumColorSync = const Value.absent(), + this.amoledDarkTheme = const Value.absent(), + this.checkUpdate = const Value.absent(), + this.normalizeAudio = const Value.absent(), + this.showSystemTrayIcon = const Value.absent(), + this.systemTitleBar = const Value.absent(), + this.skipNonMusic = const Value.absent(), + this.closeBehavior = const Value.absent(), + this.accentColorScheme = const Value.absent(), + this.layoutMode = const Value.absent(), + this.locale = const Value.absent(), + this.market = const Value.absent(), + this.searchMode = const Value.absent(), + this.downloadLocation = const Value.absent(), + this.localLibraryLocation = const Value.absent(), + this.pipedInstance = const Value.absent(), + this.invidiousInstance = const Value.absent(), + this.themeMode = const Value.absent(), + this.audioSourceId = const Value.absent(), + this.youtubeClientEngine = const Value.absent(), + this.discordPresence = const Value.absent(), + this.endlessPlayback = const Value.absent(), + this.enableConnect = const Value.absent(), + this.connectPort = const Value.absent(), + this.cacheMusic = const Value.absent(), + }); + PreferencesTableCompanion.insert({ + this.id = const Value.absent(), + this.albumColorSync = const Value.absent(), + this.amoledDarkTheme = const Value.absent(), + this.checkUpdate = const Value.absent(), + this.normalizeAudio = const Value.absent(), + this.showSystemTrayIcon = const Value.absent(), + this.systemTitleBar = const Value.absent(), + this.skipNonMusic = const Value.absent(), + this.closeBehavior = const Value.absent(), + this.accentColorScheme = const Value.absent(), + this.layoutMode = const Value.absent(), + this.locale = const Value.absent(), + this.market = const Value.absent(), + this.searchMode = const Value.absent(), + this.downloadLocation = const Value.absent(), + this.localLibraryLocation = const Value.absent(), + this.pipedInstance = const Value.absent(), + this.invidiousInstance = const Value.absent(), + this.themeMode = const Value.absent(), + this.audioSourceId = const Value.absent(), + this.youtubeClientEngine = const Value.absent(), + this.discordPresence = const Value.absent(), + this.endlessPlayback = const Value.absent(), + this.enableConnect = const Value.absent(), + this.connectPort = const Value.absent(), + this.cacheMusic = const Value.absent(), + }); + static Insertable custom({ + Expression? id, + Expression? albumColorSync, + Expression? amoledDarkTheme, + Expression? checkUpdate, + Expression? normalizeAudio, + Expression? showSystemTrayIcon, + Expression? systemTitleBar, + Expression? skipNonMusic, + Expression? closeBehavior, + Expression? accentColorScheme, + Expression? layoutMode, + Expression? locale, + Expression? market, + Expression? searchMode, + Expression? downloadLocation, + Expression? localLibraryLocation, + Expression? pipedInstance, + Expression? invidiousInstance, + Expression? themeMode, + Expression? audioSourceId, + Expression? youtubeClientEngine, + Expression? discordPresence, + Expression? endlessPlayback, + Expression? enableConnect, + Expression? connectPort, + Expression? cacheMusic, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (albumColorSync != null) 'album_color_sync': albumColorSync, + if (amoledDarkTheme != null) 'amoled_dark_theme': amoledDarkTheme, + if (checkUpdate != null) 'check_update': checkUpdate, + if (normalizeAudio != null) 'normalize_audio': normalizeAudio, + if (showSystemTrayIcon != null) + 'show_system_tray_icon': showSystemTrayIcon, + if (systemTitleBar != null) 'system_title_bar': systemTitleBar, + if (skipNonMusic != null) 'skip_non_music': skipNonMusic, + if (closeBehavior != null) 'close_behavior': closeBehavior, + if (accentColorScheme != null) 'accent_color_scheme': accentColorScheme, + if (layoutMode != null) 'layout_mode': layoutMode, + if (locale != null) 'locale': locale, + if (market != null) 'market': market, + if (searchMode != null) 'search_mode': searchMode, + if (downloadLocation != null) 'download_location': downloadLocation, + if (localLibraryLocation != null) + 'local_library_location': localLibraryLocation, + if (pipedInstance != null) 'piped_instance': pipedInstance, + if (invidiousInstance != null) 'invidious_instance': invidiousInstance, + if (themeMode != null) 'theme_mode': themeMode, + if (audioSourceId != null) 'audio_source_id': audioSourceId, + if (youtubeClientEngine != null) + 'youtube_client_engine': youtubeClientEngine, + if (discordPresence != null) 'discord_presence': discordPresence, + if (endlessPlayback != null) 'endless_playback': endlessPlayback, + if (enableConnect != null) 'enable_connect': enableConnect, + if (connectPort != null) 'connect_port': connectPort, + if (cacheMusic != null) 'cache_music': cacheMusic, + }); + } + + PreferencesTableCompanion copyWith( + {Value? id, + Value? albumColorSync, + Value? amoledDarkTheme, + Value? checkUpdate, + Value? normalizeAudio, + Value? showSystemTrayIcon, + Value? systemTitleBar, + Value? skipNonMusic, + Value? closeBehavior, + Value? accentColorScheme, + Value? layoutMode, + Value? locale, + Value? market, + Value? searchMode, + Value? downloadLocation, + Value? localLibraryLocation, + Value? pipedInstance, + Value? invidiousInstance, + Value? themeMode, + Value? audioSourceId, + Value? youtubeClientEngine, + Value? discordPresence, + Value? endlessPlayback, + Value? enableConnect, + Value? connectPort, + Value? cacheMusic}) { + return PreferencesTableCompanion( + id: id ?? this.id, + albumColorSync: albumColorSync ?? this.albumColorSync, + amoledDarkTheme: amoledDarkTheme ?? this.amoledDarkTheme, + checkUpdate: checkUpdate ?? this.checkUpdate, + normalizeAudio: normalizeAudio ?? this.normalizeAudio, + showSystemTrayIcon: showSystemTrayIcon ?? this.showSystemTrayIcon, + systemTitleBar: systemTitleBar ?? this.systemTitleBar, + skipNonMusic: skipNonMusic ?? this.skipNonMusic, + closeBehavior: closeBehavior ?? this.closeBehavior, + accentColorScheme: accentColorScheme ?? this.accentColorScheme, + layoutMode: layoutMode ?? this.layoutMode, + locale: locale ?? this.locale, + market: market ?? this.market, + searchMode: searchMode ?? this.searchMode, + downloadLocation: downloadLocation ?? this.downloadLocation, + localLibraryLocation: localLibraryLocation ?? this.localLibraryLocation, + pipedInstance: pipedInstance ?? this.pipedInstance, + invidiousInstance: invidiousInstance ?? this.invidiousInstance, + themeMode: themeMode ?? this.themeMode, + audioSourceId: audioSourceId ?? this.audioSourceId, + youtubeClientEngine: youtubeClientEngine ?? this.youtubeClientEngine, + discordPresence: discordPresence ?? this.discordPresence, + endlessPlayback: endlessPlayback ?? this.endlessPlayback, + enableConnect: enableConnect ?? this.enableConnect, + connectPort: connectPort ?? this.connectPort, + cacheMusic: cacheMusic ?? this.cacheMusic, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (albumColorSync.present) { + map['album_color_sync'] = Variable(albumColorSync.value); + } + if (amoledDarkTheme.present) { + map['amoled_dark_theme'] = Variable(amoledDarkTheme.value); + } + if (checkUpdate.present) { + map['check_update'] = Variable(checkUpdate.value); + } + if (normalizeAudio.present) { + map['normalize_audio'] = Variable(normalizeAudio.value); + } + if (showSystemTrayIcon.present) { + map['show_system_tray_icon'] = Variable(showSystemTrayIcon.value); + } + if (systemTitleBar.present) { + map['system_title_bar'] = Variable(systemTitleBar.value); + } + if (skipNonMusic.present) { + map['skip_non_music'] = Variable(skipNonMusic.value); + } + if (closeBehavior.present) { + map['close_behavior'] = Variable(closeBehavior.value); + } + if (accentColorScheme.present) { + map['accent_color_scheme'] = Variable(accentColorScheme.value); + } + if (layoutMode.present) { + map['layout_mode'] = Variable(layoutMode.value); + } + if (locale.present) { + map['locale'] = Variable(locale.value); + } + if (market.present) { + map['market'] = Variable(market.value); + } + if (searchMode.present) { + map['search_mode'] = Variable(searchMode.value); + } + if (downloadLocation.present) { + map['download_location'] = Variable(downloadLocation.value); + } + if (localLibraryLocation.present) { + map['local_library_location'] = + Variable(localLibraryLocation.value); + } + if (pipedInstance.present) { + map['piped_instance'] = Variable(pipedInstance.value); + } + if (invidiousInstance.present) { + map['invidious_instance'] = Variable(invidiousInstance.value); + } + if (themeMode.present) { + map['theme_mode'] = Variable(themeMode.value); + } + if (audioSourceId.present) { + map['audio_source_id'] = Variable(audioSourceId.value); + } + if (youtubeClientEngine.present) { + map['youtube_client_engine'] = + Variable(youtubeClientEngine.value); + } + if (discordPresence.present) { + map['discord_presence'] = Variable(discordPresence.value); + } + if (endlessPlayback.present) { + map['endless_playback'] = Variable(endlessPlayback.value); + } + if (enableConnect.present) { + map['enable_connect'] = Variable(enableConnect.value); + } + if (connectPort.present) { + map['connect_port'] = Variable(connectPort.value); + } + if (cacheMusic.present) { + map['cache_music'] = Variable(cacheMusic.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PreferencesTableCompanion(') + ..write('id: $id, ') + ..write('albumColorSync: $albumColorSync, ') + ..write('amoledDarkTheme: $amoledDarkTheme, ') + ..write('checkUpdate: $checkUpdate, ') + ..write('normalizeAudio: $normalizeAudio, ') + ..write('showSystemTrayIcon: $showSystemTrayIcon, ') + ..write('systemTitleBar: $systemTitleBar, ') + ..write('skipNonMusic: $skipNonMusic, ') + ..write('closeBehavior: $closeBehavior, ') + ..write('accentColorScheme: $accentColorScheme, ') + ..write('layoutMode: $layoutMode, ') + ..write('locale: $locale, ') + ..write('market: $market, ') + ..write('searchMode: $searchMode, ') + ..write('downloadLocation: $downloadLocation, ') + ..write('localLibraryLocation: $localLibraryLocation, ') + ..write('pipedInstance: $pipedInstance, ') + ..write('invidiousInstance: $invidiousInstance, ') + ..write('themeMode: $themeMode, ') + ..write('audioSourceId: $audioSourceId, ') + ..write('youtubeClientEngine: $youtubeClientEngine, ') + ..write('discordPresence: $discordPresence, ') + ..write('endlessPlayback: $endlessPlayback, ') + ..write('enableConnect: $enableConnect, ') + ..write('connectPort: $connectPort, ') + ..write('cacheMusic: $cacheMusic') + ..write(')')) + .toString(); + } +} + +class ScrobblerTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + ScrobblerTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + late final GeneratedColumn username = GeneratedColumn( + 'username', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn passwordHash = GeneratedColumn( + 'password_hash', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + @override + List get $columns => [id, createdAt, username, passwordHash]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'scrobbler_table'; + @override + Set get $primaryKey => {id}; + @override + ScrobblerTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ScrobblerTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + username: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}username'])!, + passwordHash: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}password_hash'])!, + ); + } + + @override + ScrobblerTable createAlias(String alias) { + return ScrobblerTable(attachedDatabase, alias); + } +} + +class ScrobblerTableData extends DataClass + implements Insertable { + final int id; + final DateTime createdAt; + final String username; + final String passwordHash; + const ScrobblerTableData( + {required this.id, + required this.createdAt, + required this.username, + required this.passwordHash}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['username'] = Variable(username); + map['password_hash'] = Variable(passwordHash); + return map; + } + + ScrobblerTableCompanion toCompanion(bool nullToAbsent) { + return ScrobblerTableCompanion( + id: Value(id), + createdAt: Value(createdAt), + username: Value(username), + passwordHash: Value(passwordHash), + ); + } + + factory ScrobblerTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ScrobblerTableData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + username: serializer.fromJson(json['username']), + passwordHash: serializer.fromJson(json['passwordHash']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'username': serializer.toJson(username), + 'passwordHash': serializer.toJson(passwordHash), + }; + } + + ScrobblerTableData copyWith( + {int? id, + DateTime? createdAt, + String? username, + String? passwordHash}) => + ScrobblerTableData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + username: username ?? this.username, + passwordHash: passwordHash ?? this.passwordHash, + ); + ScrobblerTableData copyWithCompanion(ScrobblerTableCompanion data) { + return ScrobblerTableData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + username: data.username.present ? data.username.value : this.username, + passwordHash: data.passwordHash.present + ? data.passwordHash.value + : this.passwordHash, + ); + } + + @override + String toString() { + return (StringBuffer('ScrobblerTableData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('username: $username, ') + ..write('passwordHash: $passwordHash') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, createdAt, username, passwordHash); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ScrobblerTableData && + other.id == this.id && + other.createdAt == this.createdAt && + other.username == this.username && + other.passwordHash == this.passwordHash); +} + +class ScrobblerTableCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value username; + final Value passwordHash; + const ScrobblerTableCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.username = const Value.absent(), + this.passwordHash = const Value.absent(), + }); + ScrobblerTableCompanion.insert({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + required String username, + required String passwordHash, + }) : username = Value(username), + passwordHash = Value(passwordHash); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? username, + Expression? passwordHash, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (username != null) 'username': username, + if (passwordHash != null) 'password_hash': passwordHash, + }); + } + + ScrobblerTableCompanion copyWith( + {Value? id, + Value? createdAt, + Value? username, + Value? passwordHash}) { + return ScrobblerTableCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + username: username ?? this.username, + passwordHash: passwordHash ?? this.passwordHash, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (username.present) { + map['username'] = Variable(username.value); + } + if (passwordHash.present) { + map['password_hash'] = Variable(passwordHash.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ScrobblerTableCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('username: $username, ') + ..write('passwordHash: $passwordHash') + ..write(')')) + .toString(); + } +} + +class SkipSegmentTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + SkipSegmentTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn start = GeneratedColumn( + 'start', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + late final GeneratedColumn end = GeneratedColumn( + 'end', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + late final GeneratedColumn trackId = GeneratedColumn( + 'track_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => [id, start, end, trackId, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'skip_segment_table'; + @override + Set get $primaryKey => {id}; + @override + SkipSegmentTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SkipSegmentTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + start: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}start'])!, + end: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}end'])!, + trackId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}track_id'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + SkipSegmentTable createAlias(String alias) { + return SkipSegmentTable(attachedDatabase, alias); + } +} + +class SkipSegmentTableData extends DataClass + implements Insertable { + final int id; + final int start; + final int end; + final String trackId; + final DateTime createdAt; + const SkipSegmentTableData( + {required this.id, + required this.start, + required this.end, + required this.trackId, + required this.createdAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['start'] = Variable(start); + map['end'] = Variable(end); + map['track_id'] = Variable(trackId); + map['created_at'] = Variable(createdAt); + return map; + } + + SkipSegmentTableCompanion toCompanion(bool nullToAbsent) { + return SkipSegmentTableCompanion( + id: Value(id), + start: Value(start), + end: Value(end), + trackId: Value(trackId), + createdAt: Value(createdAt), + ); + } + + factory SkipSegmentTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SkipSegmentTableData( + id: serializer.fromJson(json['id']), + start: serializer.fromJson(json['start']), + end: serializer.fromJson(json['end']), + trackId: serializer.fromJson(json['trackId']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'start': serializer.toJson(start), + 'end': serializer.toJson(end), + 'trackId': serializer.toJson(trackId), + 'createdAt': serializer.toJson(createdAt), + }; + } + + SkipSegmentTableData copyWith( + {int? id, + int? start, + int? end, + String? trackId, + DateTime? createdAt}) => + SkipSegmentTableData( + id: id ?? this.id, + start: start ?? this.start, + end: end ?? this.end, + trackId: trackId ?? this.trackId, + createdAt: createdAt ?? this.createdAt, + ); + SkipSegmentTableData copyWithCompanion(SkipSegmentTableCompanion data) { + return SkipSegmentTableData( + id: data.id.present ? data.id.value : this.id, + start: data.start.present ? data.start.value : this.start, + end: data.end.present ? data.end.value : this.end, + trackId: data.trackId.present ? data.trackId.value : this.trackId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('SkipSegmentTableData(') + ..write('id: $id, ') + ..write('start: $start, ') + ..write('end: $end, ') + ..write('trackId: $trackId, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, start, end, trackId, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SkipSegmentTableData && + other.id == this.id && + other.start == this.start && + other.end == this.end && + other.trackId == this.trackId && + other.createdAt == this.createdAt); +} + +class SkipSegmentTableCompanion extends UpdateCompanion { + final Value id; + final Value start; + final Value end; + final Value trackId; + final Value createdAt; + const SkipSegmentTableCompanion({ + this.id = const Value.absent(), + this.start = const Value.absent(), + this.end = const Value.absent(), + this.trackId = const Value.absent(), + this.createdAt = const Value.absent(), + }); + SkipSegmentTableCompanion.insert({ + this.id = const Value.absent(), + required int start, + required int end, + required String trackId, + this.createdAt = const Value.absent(), + }) : start = Value(start), + end = Value(end), + trackId = Value(trackId); + static Insertable custom({ + Expression? id, + Expression? start, + Expression? end, + Expression? trackId, + Expression? createdAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (start != null) 'start': start, + if (end != null) 'end': end, + if (trackId != null) 'track_id': trackId, + if (createdAt != null) 'created_at': createdAt, + }); + } + + SkipSegmentTableCompanion copyWith( + {Value? id, + Value? start, + Value? end, + Value? trackId, + Value? createdAt}) { + return SkipSegmentTableCompanion( + id: id ?? this.id, + start: start ?? this.start, + end: end ?? this.end, + trackId: trackId ?? this.trackId, + createdAt: createdAt ?? this.createdAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (start.present) { + map['start'] = Variable(start.value); + } + if (end.present) { + map['end'] = Variable(end.value); + } + if (trackId.present) { + map['track_id'] = Variable(trackId.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SkipSegmentTableCompanion(') + ..write('id: $id, ') + ..write('start: $start, ') + ..write('end: $end, ') + ..write('trackId: $trackId, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } +} + +class SourceMatchTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + SourceMatchTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn trackId = GeneratedColumn( + 'track_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn sourceInfo = GeneratedColumn( + 'source_info', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => + [id, trackId, sourceInfo, sourceType, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'source_match_table'; + @override + Set get $primaryKey => {id}; + @override + SourceMatchTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SourceMatchTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + trackId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}track_id'])!, + sourceInfo: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}source_info'])!, + sourceType: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}source_type'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + SourceMatchTable createAlias(String alias) { + return SourceMatchTable(attachedDatabase, alias); + } +} + +class SourceMatchTableData extends DataClass + implements Insertable { + final int id; + final String trackId; + final String sourceInfo; + final String sourceType; + final DateTime createdAt; + const SourceMatchTableData( + {required this.id, + required this.trackId, + required this.sourceInfo, + required this.sourceType, + required this.createdAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['track_id'] = Variable(trackId); + map['source_info'] = Variable(sourceInfo); + map['source_type'] = Variable(sourceType); + map['created_at'] = Variable(createdAt); + return map; + } + + SourceMatchTableCompanion toCompanion(bool nullToAbsent) { + return SourceMatchTableCompanion( + id: Value(id), + trackId: Value(trackId), + sourceInfo: Value(sourceInfo), + sourceType: Value(sourceType), + createdAt: Value(createdAt), + ); + } + + factory SourceMatchTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SourceMatchTableData( + id: serializer.fromJson(json['id']), + trackId: serializer.fromJson(json['trackId']), + sourceInfo: serializer.fromJson(json['sourceInfo']), + sourceType: serializer.fromJson(json['sourceType']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'trackId': serializer.toJson(trackId), + 'sourceInfo': serializer.toJson(sourceInfo), + 'sourceType': serializer.toJson(sourceType), + 'createdAt': serializer.toJson(createdAt), + }; + } + + SourceMatchTableData copyWith( + {int? id, + String? trackId, + String? sourceInfo, + String? sourceType, + DateTime? createdAt}) => + SourceMatchTableData( + id: id ?? this.id, + trackId: trackId ?? this.trackId, + sourceInfo: sourceInfo ?? this.sourceInfo, + sourceType: sourceType ?? this.sourceType, + createdAt: createdAt ?? this.createdAt, + ); + SourceMatchTableData copyWithCompanion(SourceMatchTableCompanion data) { + return SourceMatchTableData( + id: data.id.present ? data.id.value : this.id, + trackId: data.trackId.present ? data.trackId.value : this.trackId, + sourceInfo: + data.sourceInfo.present ? data.sourceInfo.value : this.sourceInfo, + sourceType: + data.sourceType.present ? data.sourceType.value : this.sourceType, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('SourceMatchTableData(') + ..write('id: $id, ') + ..write('trackId: $trackId, ') + ..write('sourceInfo: $sourceInfo, ') + ..write('sourceType: $sourceType, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, trackId, sourceInfo, sourceType, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SourceMatchTableData && + other.id == this.id && + other.trackId == this.trackId && + other.sourceInfo == this.sourceInfo && + other.sourceType == this.sourceType && + other.createdAt == this.createdAt); +} + +class SourceMatchTableCompanion extends UpdateCompanion { + final Value id; + final Value trackId; + final Value sourceInfo; + final Value sourceType; + final Value createdAt; + const SourceMatchTableCompanion({ + this.id = const Value.absent(), + this.trackId = const Value.absent(), + this.sourceInfo = const Value.absent(), + this.sourceType = const Value.absent(), + this.createdAt = const Value.absent(), + }); + SourceMatchTableCompanion.insert({ + this.id = const Value.absent(), + required String trackId, + required String sourceInfo, + required String sourceType, + this.createdAt = const Value.absent(), + }) : trackId = Value(trackId), + sourceInfo = Value(sourceInfo), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? trackId, + Expression? sourceInfo, + Expression? sourceType, + Expression? createdAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (trackId != null) 'track_id': trackId, + if (sourceInfo != null) 'source_info': sourceInfo, + if (sourceType != null) 'source_type': sourceType, + if (createdAt != null) 'created_at': createdAt, + }); + } + + SourceMatchTableCompanion copyWith( + {Value? id, + Value? trackId, + Value? sourceInfo, + Value? sourceType, + Value? createdAt}) { + return SourceMatchTableCompanion( + id: id ?? this.id, + trackId: trackId ?? this.trackId, + sourceInfo: sourceInfo ?? this.sourceInfo, + sourceType: sourceType ?? this.sourceType, + createdAt: createdAt ?? this.createdAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (trackId.present) { + map['track_id'] = Variable(trackId.value); + } + if (sourceInfo.present) { + map['source_info'] = Variable(sourceInfo.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SourceMatchTableCompanion(') + ..write('id: $id, ') + ..write('trackId: $trackId, ') + ..write('sourceInfo: $sourceInfo, ') + ..write('sourceType: $sourceType, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } +} + +class AudioPlayerStateTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AudioPlayerStateTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn playing = GeneratedColumn( + 'playing', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("playing" IN (0, 1))')); + late final GeneratedColumn loopMode = GeneratedColumn( + 'loop_mode', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn shuffled = GeneratedColumn( + 'shuffled', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("shuffled" IN (0, 1))')); + late final GeneratedColumn collections = GeneratedColumn( + 'collections', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn tracks = GeneratedColumn( + 'tracks', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("[]")); + late final GeneratedColumn currentIndex = GeneratedColumn( + 'current_index', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); + @override + List get $columns => + [id, playing, loopMode, shuffled, collections, tracks, currentIndex]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'audio_player_state_table'; + @override + Set get $primaryKey => {id}; + @override + AudioPlayerStateTableData map(Map data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AudioPlayerStateTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + playing: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}playing'])!, + loopMode: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}loop_mode'])!, + shuffled: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}shuffled'])!, + collections: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}collections'])!, + tracks: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}tracks'])!, + currentIndex: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}current_index'])!, + ); + } + + @override + AudioPlayerStateTable createAlias(String alias) { + return AudioPlayerStateTable(attachedDatabase, alias); + } +} + +class AudioPlayerStateTableData extends DataClass + implements Insertable { + final int id; + final bool playing; + final String loopMode; + final bool shuffled; + final String collections; + final String tracks; + final int currentIndex; + const AudioPlayerStateTableData( + {required this.id, + required this.playing, + required this.loopMode, + required this.shuffled, + required this.collections, + required this.tracks, + required this.currentIndex}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['playing'] = Variable(playing); + map['loop_mode'] = Variable(loopMode); + map['shuffled'] = Variable(shuffled); + map['collections'] = Variable(collections); + map['tracks'] = Variable(tracks); + map['current_index'] = Variable(currentIndex); + return map; + } + + AudioPlayerStateTableCompanion toCompanion(bool nullToAbsent) { + return AudioPlayerStateTableCompanion( + id: Value(id), + playing: Value(playing), + loopMode: Value(loopMode), + shuffled: Value(shuffled), + collections: Value(collections), + tracks: Value(tracks), + currentIndex: Value(currentIndex), + ); + } + + factory AudioPlayerStateTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AudioPlayerStateTableData( + id: serializer.fromJson(json['id']), + playing: serializer.fromJson(json['playing']), + loopMode: serializer.fromJson(json['loopMode']), + shuffled: serializer.fromJson(json['shuffled']), + collections: serializer.fromJson(json['collections']), + tracks: serializer.fromJson(json['tracks']), + currentIndex: serializer.fromJson(json['currentIndex']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'playing': serializer.toJson(playing), + 'loopMode': serializer.toJson(loopMode), + 'shuffled': serializer.toJson(shuffled), + 'collections': serializer.toJson(collections), + 'tracks': serializer.toJson(tracks), + 'currentIndex': serializer.toJson(currentIndex), + }; + } + + AudioPlayerStateTableData copyWith( + {int? id, + bool? playing, + String? loopMode, + bool? shuffled, + String? collections, + String? tracks, + int? currentIndex}) => + AudioPlayerStateTableData( + id: id ?? this.id, + playing: playing ?? this.playing, + loopMode: loopMode ?? this.loopMode, + shuffled: shuffled ?? this.shuffled, + collections: collections ?? this.collections, + tracks: tracks ?? this.tracks, + currentIndex: currentIndex ?? this.currentIndex, + ); + AudioPlayerStateTableData copyWithCompanion( + AudioPlayerStateTableCompanion data) { + return AudioPlayerStateTableData( + id: data.id.present ? data.id.value : this.id, + playing: data.playing.present ? data.playing.value : this.playing, + loopMode: data.loopMode.present ? data.loopMode.value : this.loopMode, + shuffled: data.shuffled.present ? data.shuffled.value : this.shuffled, + collections: + data.collections.present ? data.collections.value : this.collections, + tracks: data.tracks.present ? data.tracks.value : this.tracks, + currentIndex: data.currentIndex.present + ? data.currentIndex.value + : this.currentIndex, + ); + } + + @override + String toString() { + return (StringBuffer('AudioPlayerStateTableData(') + ..write('id: $id, ') + ..write('playing: $playing, ') + ..write('loopMode: $loopMode, ') + ..write('shuffled: $shuffled, ') + ..write('collections: $collections, ') + ..write('tracks: $tracks, ') + ..write('currentIndex: $currentIndex') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, playing, loopMode, shuffled, collections, tracks, currentIndex); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AudioPlayerStateTableData && + other.id == this.id && + other.playing == this.playing && + other.loopMode == this.loopMode && + other.shuffled == this.shuffled && + other.collections == this.collections && + other.tracks == this.tracks && + other.currentIndex == this.currentIndex); +} + +class AudioPlayerStateTableCompanion + extends UpdateCompanion { + final Value id; + final Value playing; + final Value loopMode; + final Value shuffled; + final Value collections; + final Value tracks; + final Value currentIndex; + const AudioPlayerStateTableCompanion({ + this.id = const Value.absent(), + this.playing = const Value.absent(), + this.loopMode = const Value.absent(), + this.shuffled = const Value.absent(), + this.collections = const Value.absent(), + this.tracks = const Value.absent(), + this.currentIndex = const Value.absent(), + }); + AudioPlayerStateTableCompanion.insert({ + this.id = const Value.absent(), + required bool playing, + required String loopMode, + required bool shuffled, + required String collections, + this.tracks = const Value.absent(), + this.currentIndex = const Value.absent(), + }) : playing = Value(playing), + loopMode = Value(loopMode), + shuffled = Value(shuffled), + collections = Value(collections); + static Insertable custom({ + Expression? id, + Expression? playing, + Expression? loopMode, + Expression? shuffled, + Expression? collections, + Expression? tracks, + Expression? currentIndex, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (playing != null) 'playing': playing, + if (loopMode != null) 'loop_mode': loopMode, + if (shuffled != null) 'shuffled': shuffled, + if (collections != null) 'collections': collections, + if (tracks != null) 'tracks': tracks, + if (currentIndex != null) 'current_index': currentIndex, + }); + } + + AudioPlayerStateTableCompanion copyWith( + {Value? id, + Value? playing, + Value? loopMode, + Value? shuffled, + Value? collections, + Value? tracks, + Value? currentIndex}) { + return AudioPlayerStateTableCompanion( + id: id ?? this.id, + playing: playing ?? this.playing, + loopMode: loopMode ?? this.loopMode, + shuffled: shuffled ?? this.shuffled, + collections: collections ?? this.collections, + tracks: tracks ?? this.tracks, + currentIndex: currentIndex ?? this.currentIndex, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (playing.present) { + map['playing'] = Variable(playing.value); + } + if (loopMode.present) { + map['loop_mode'] = Variable(loopMode.value); + } + if (shuffled.present) { + map['shuffled'] = Variable(shuffled.value); + } + if (collections.present) { + map['collections'] = Variable(collections.value); + } + if (tracks.present) { + map['tracks'] = Variable(tracks.value); + } + if (currentIndex.present) { + map['current_index'] = Variable(currentIndex.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AudioPlayerStateTableCompanion(') + ..write('id: $id, ') + ..write('playing: $playing, ') + ..write('loopMode: $loopMode, ') + ..write('shuffled: $shuffled, ') + ..write('collections: $collections, ') + ..write('tracks: $tracks, ') + ..write('currentIndex: $currentIndex') + ..write(')')) + .toString(); + } +} + +class HistoryTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + HistoryTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + late final GeneratedColumn type = GeneratedColumn( + 'type', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn itemId = GeneratedColumn( + 'item_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn data = GeneratedColumn( + 'data', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + @override + List get $columns => [id, createdAt, type, itemId, data]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'history_table'; + @override + Set get $primaryKey => {id}; + @override + HistoryTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return HistoryTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + type: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}type'])!, + itemId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}item_id'])!, + data: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}data'])!, + ); + } + + @override + HistoryTable createAlias(String alias) { + return HistoryTable(attachedDatabase, alias); + } +} + +class HistoryTableData extends DataClass + implements Insertable { + final int id; + final DateTime createdAt; + final String type; + final String itemId; + final String data; + const HistoryTableData( + {required this.id, + required this.createdAt, + required this.type, + required this.itemId, + required this.data}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['type'] = Variable(type); + map['item_id'] = Variable(itemId); + map['data'] = Variable(data); + return map; + } + + HistoryTableCompanion toCompanion(bool nullToAbsent) { + return HistoryTableCompanion( + id: Value(id), + createdAt: Value(createdAt), + type: Value(type), + itemId: Value(itemId), + data: Value(data), + ); + } + + factory HistoryTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return HistoryTableData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + type: serializer.fromJson(json['type']), + itemId: serializer.fromJson(json['itemId']), + data: serializer.fromJson(json['data']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'type': serializer.toJson(type), + 'itemId': serializer.toJson(itemId), + 'data': serializer.toJson(data), + }; + } + + HistoryTableData copyWith( + {int? id, + DateTime? createdAt, + String? type, + String? itemId, + String? data}) => + HistoryTableData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + type: type ?? this.type, + itemId: itemId ?? this.itemId, + data: data ?? this.data, + ); + HistoryTableData copyWithCompanion(HistoryTableCompanion data) { + return HistoryTableData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + type: data.type.present ? data.type.value : this.type, + itemId: data.itemId.present ? data.itemId.value : this.itemId, + data: data.data.present ? data.data.value : this.data, + ); + } + + @override + String toString() { + return (StringBuffer('HistoryTableData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('type: $type, ') + ..write('itemId: $itemId, ') + ..write('data: $data') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, createdAt, type, itemId, data); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is HistoryTableData && + other.id == this.id && + other.createdAt == this.createdAt && + other.type == this.type && + other.itemId == this.itemId && + other.data == this.data); +} + +class HistoryTableCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value type; + final Value itemId; + final Value data; + const HistoryTableCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.type = const Value.absent(), + this.itemId = const Value.absent(), + this.data = const Value.absent(), + }); + HistoryTableCompanion.insert({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + required String type, + required String itemId, + required String data, + }) : type = Value(type), + itemId = Value(itemId), + data = Value(data); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? type, + Expression? itemId, + Expression? data, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (type != null) 'type': type, + if (itemId != null) 'item_id': itemId, + if (data != null) 'data': data, + }); + } + + HistoryTableCompanion copyWith( + {Value? id, + Value? createdAt, + Value? type, + Value? itemId, + Value? data}) { + return HistoryTableCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + type: type ?? this.type, + itemId: itemId ?? this.itemId, + data: data ?? this.data, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (itemId.present) { + map['item_id'] = Variable(itemId.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('HistoryTableCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('type: $type, ') + ..write('itemId: $itemId, ') + ..write('data: $data') + ..write(')')) + .toString(); + } +} + +class LyricsTable extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LyricsTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn trackId = GeneratedColumn( + 'track_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn data = GeneratedColumn( + 'data', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + @override + List get $columns => [id, trackId, data]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'lyrics_table'; + @override + Set get $primaryKey => {id}; + @override + LyricsTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LyricsTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + trackId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}track_id'])!, + data: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}data'])!, + ); + } + + @override + LyricsTable createAlias(String alias) { + return LyricsTable(attachedDatabase, alias); + } +} + +class LyricsTableData extends DataClass implements Insertable { + final int id; + final String trackId; + final String data; + const LyricsTableData( + {required this.id, required this.trackId, required this.data}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['track_id'] = Variable(trackId); + map['data'] = Variable(data); + return map; + } + + LyricsTableCompanion toCompanion(bool nullToAbsent) { + return LyricsTableCompanion( + id: Value(id), + trackId: Value(trackId), + data: Value(data), + ); + } + + factory LyricsTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LyricsTableData( + id: serializer.fromJson(json['id']), + trackId: serializer.fromJson(json['trackId']), + data: serializer.fromJson(json['data']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'trackId': serializer.toJson(trackId), + 'data': serializer.toJson(data), + }; + } + + LyricsTableData copyWith({int? id, String? trackId, String? data}) => + LyricsTableData( + id: id ?? this.id, + trackId: trackId ?? this.trackId, + data: data ?? this.data, + ); + LyricsTableData copyWithCompanion(LyricsTableCompanion data) { + return LyricsTableData( + id: data.id.present ? data.id.value : this.id, + trackId: data.trackId.present ? data.trackId.value : this.trackId, + data: data.data.present ? data.data.value : this.data, + ); + } + + @override + String toString() { + return (StringBuffer('LyricsTableData(') + ..write('id: $id, ') + ..write('trackId: $trackId, ') + ..write('data: $data') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, trackId, data); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LyricsTableData && + other.id == this.id && + other.trackId == this.trackId && + other.data == this.data); +} + +class LyricsTableCompanion extends UpdateCompanion { + final Value id; + final Value trackId; + final Value data; + const LyricsTableCompanion({ + this.id = const Value.absent(), + this.trackId = const Value.absent(), + this.data = const Value.absent(), + }); + LyricsTableCompanion.insert({ + this.id = const Value.absent(), + required String trackId, + required String data, + }) : trackId = Value(trackId), + data = Value(data); + static Insertable custom({ + Expression? id, + Expression? trackId, + Expression? data, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (trackId != null) 'track_id': trackId, + if (data != null) 'data': data, + }); + } + + LyricsTableCompanion copyWith( + {Value? id, Value? trackId, Value? data}) { + return LyricsTableCompanion( + id: id ?? this.id, + trackId: trackId ?? this.trackId, + data: data ?? this.data, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (trackId.present) { + map['track_id'] = Variable(trackId.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LyricsTableCompanion(') + ..write('id: $id, ') + ..write('trackId: $trackId, ') + ..write('data: $data') + ..write(')')) + .toString(); + } +} + +class PluginsTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PluginsTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, false, + additionalChecks: + GeneratedColumn.checkTextLength(minTextLength: 1, maxTextLength: 50), + type: DriftSqlType.string, + requiredDuringInsert: true); + late final GeneratedColumn description = GeneratedColumn( + 'description', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn version = GeneratedColumn( + 'version', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn author = GeneratedColumn( + 'author', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn entryPoint = GeneratedColumn( + 'entry_point', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn apis = GeneratedColumn( + 'apis', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn abilities = GeneratedColumn( + 'abilities', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn selectedForMetadata = GeneratedColumn( + 'selected_for_metadata', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("selected_for_metadata" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn selectedForAudioSource = + GeneratedColumn('selected_for_audio_source', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("selected_for_audio_source" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn repository = GeneratedColumn( + 'repository', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn pluginApiVersion = GeneratedColumn( + 'plugin_api_version', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('2.0.0')); + @override + List get $columns => [ + id, + name, + description, + version, + author, + entryPoint, + apis, + abilities, + selectedForMetadata, + selectedForAudioSource, + repository, + pluginApiVersion + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'plugins_table'; + @override + Set get $primaryKey => {id}; + @override + PluginsTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PluginsTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name'])!, + description: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}description'])!, + version: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}version'])!, + author: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}author'])!, + entryPoint: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}entry_point'])!, + apis: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}apis'])!, + abilities: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}abilities'])!, + selectedForMetadata: attachedDatabase.typeMapping.read( + DriftSqlType.bool, data['${effectivePrefix}selected_for_metadata'])!, + selectedForAudioSource: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}selected_for_audio_source'])!, + repository: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}repository']), + pluginApiVersion: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}plugin_api_version'])!, + ); + } + + @override + PluginsTable createAlias(String alias) { + return PluginsTable(attachedDatabase, alias); + } +} + +class PluginsTableData extends DataClass + implements Insertable { + final int id; + final String name; + final String description; + final String version; + final String author; + final String entryPoint; + final String apis; + final String abilities; + final bool selectedForMetadata; + final bool selectedForAudioSource; + final String? repository; + final String pluginApiVersion; + const PluginsTableData( + {required this.id, + required this.name, + required this.description, + required this.version, + required this.author, + required this.entryPoint, + required this.apis, + required this.abilities, + required this.selectedForMetadata, + required this.selectedForAudioSource, + this.repository, + required this.pluginApiVersion}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['version'] = Variable(version); + map['author'] = Variable(author); + map['entry_point'] = Variable(entryPoint); + map['apis'] = Variable(apis); + map['abilities'] = Variable(abilities); + map['selected_for_metadata'] = Variable(selectedForMetadata); + map['selected_for_audio_source'] = Variable(selectedForAudioSource); + if (!nullToAbsent || repository != null) { + map['repository'] = Variable(repository); + } + map['plugin_api_version'] = Variable(pluginApiVersion); + return map; + } + + PluginsTableCompanion toCompanion(bool nullToAbsent) { + return PluginsTableCompanion( + id: Value(id), + name: Value(name), + description: Value(description), + version: Value(version), + author: Value(author), + entryPoint: Value(entryPoint), + apis: Value(apis), + abilities: Value(abilities), + selectedForMetadata: Value(selectedForMetadata), + selectedForAudioSource: Value(selectedForAudioSource), + repository: repository == null && nullToAbsent + ? const Value.absent() + : Value(repository), + pluginApiVersion: Value(pluginApiVersion), + ); + } + + factory PluginsTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PluginsTableData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + version: serializer.fromJson(json['version']), + author: serializer.fromJson(json['author']), + entryPoint: serializer.fromJson(json['entryPoint']), + apis: serializer.fromJson(json['apis']), + abilities: serializer.fromJson(json['abilities']), + selectedForMetadata: + serializer.fromJson(json['selectedForMetadata']), + selectedForAudioSource: + serializer.fromJson(json['selectedForAudioSource']), + repository: serializer.fromJson(json['repository']), + pluginApiVersion: serializer.fromJson(json['pluginApiVersion']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'version': serializer.toJson(version), + 'author': serializer.toJson(author), + 'entryPoint': serializer.toJson(entryPoint), + 'apis': serializer.toJson(apis), + 'abilities': serializer.toJson(abilities), + 'selectedForMetadata': serializer.toJson(selectedForMetadata), + 'selectedForAudioSource': serializer.toJson(selectedForAudioSource), + 'repository': serializer.toJson(repository), + 'pluginApiVersion': serializer.toJson(pluginApiVersion), + }; + } + + PluginsTableData copyWith( + {int? id, + String? name, + String? description, + String? version, + String? author, + String? entryPoint, + String? apis, + String? abilities, + bool? selectedForMetadata, + bool? selectedForAudioSource, + Value repository = const Value.absent(), + String? pluginApiVersion}) => + PluginsTableData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + version: version ?? this.version, + author: author ?? this.author, + entryPoint: entryPoint ?? this.entryPoint, + apis: apis ?? this.apis, + abilities: abilities ?? this.abilities, + selectedForMetadata: selectedForMetadata ?? this.selectedForMetadata, + selectedForAudioSource: + selectedForAudioSource ?? this.selectedForAudioSource, + repository: repository.present ? repository.value : this.repository, + pluginApiVersion: pluginApiVersion ?? this.pluginApiVersion, + ); + PluginsTableData copyWithCompanion(PluginsTableCompanion data) { + return PluginsTableData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: + data.description.present ? data.description.value : this.description, + version: data.version.present ? data.version.value : this.version, + author: data.author.present ? data.author.value : this.author, + entryPoint: + data.entryPoint.present ? data.entryPoint.value : this.entryPoint, + apis: data.apis.present ? data.apis.value : this.apis, + abilities: data.abilities.present ? data.abilities.value : this.abilities, + selectedForMetadata: data.selectedForMetadata.present + ? data.selectedForMetadata.value + : this.selectedForMetadata, + selectedForAudioSource: data.selectedForAudioSource.present + ? data.selectedForAudioSource.value + : this.selectedForAudioSource, + repository: + data.repository.present ? data.repository.value : this.repository, + pluginApiVersion: data.pluginApiVersion.present + ? data.pluginApiVersion.value + : this.pluginApiVersion, + ); + } + + @override + String toString() { + return (StringBuffer('PluginsTableData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('version: $version, ') + ..write('author: $author, ') + ..write('entryPoint: $entryPoint, ') + ..write('apis: $apis, ') + ..write('abilities: $abilities, ') + ..write('selectedForMetadata: $selectedForMetadata, ') + ..write('selectedForAudioSource: $selectedForAudioSource, ') + ..write('repository: $repository, ') + ..write('pluginApiVersion: $pluginApiVersion') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + version, + author, + entryPoint, + apis, + abilities, + selectedForMetadata, + selectedForAudioSource, + repository, + pluginApiVersion); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PluginsTableData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.version == this.version && + other.author == this.author && + other.entryPoint == this.entryPoint && + other.apis == this.apis && + other.abilities == this.abilities && + other.selectedForMetadata == this.selectedForMetadata && + other.selectedForAudioSource == this.selectedForAudioSource && + other.repository == this.repository && + other.pluginApiVersion == this.pluginApiVersion); +} + +class PluginsTableCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value version; + final Value author; + final Value entryPoint; + final Value apis; + final Value abilities; + final Value selectedForMetadata; + final Value selectedForAudioSource; + final Value repository; + final Value pluginApiVersion; + const PluginsTableCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.version = const Value.absent(), + this.author = const Value.absent(), + this.entryPoint = const Value.absent(), + this.apis = const Value.absent(), + this.abilities = const Value.absent(), + this.selectedForMetadata = const Value.absent(), + this.selectedForAudioSource = const Value.absent(), + this.repository = const Value.absent(), + this.pluginApiVersion = const Value.absent(), + }); + PluginsTableCompanion.insert({ + this.id = const Value.absent(), + required String name, + required String description, + required String version, + required String author, + required String entryPoint, + required String apis, + required String abilities, + this.selectedForMetadata = const Value.absent(), + this.selectedForAudioSource = const Value.absent(), + this.repository = const Value.absent(), + this.pluginApiVersion = const Value.absent(), + }) : name = Value(name), + description = Value(description), + version = Value(version), + author = Value(author), + entryPoint = Value(entryPoint), + apis = Value(apis), + abilities = Value(abilities); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? version, + Expression? author, + Expression? entryPoint, + Expression? apis, + Expression? abilities, + Expression? selectedForMetadata, + Expression? selectedForAudioSource, + Expression? repository, + Expression? pluginApiVersion, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (version != null) 'version': version, + if (author != null) 'author': author, + if (entryPoint != null) 'entry_point': entryPoint, + if (apis != null) 'apis': apis, + if (abilities != null) 'abilities': abilities, + if (selectedForMetadata != null) + 'selected_for_metadata': selectedForMetadata, + if (selectedForAudioSource != null) + 'selected_for_audio_source': selectedForAudioSource, + if (repository != null) 'repository': repository, + if (pluginApiVersion != null) 'plugin_api_version': pluginApiVersion, + }); + } + + PluginsTableCompanion copyWith( + {Value? id, + Value? name, + Value? description, + Value? version, + Value? author, + Value? entryPoint, + Value? apis, + Value? abilities, + Value? selectedForMetadata, + Value? selectedForAudioSource, + Value? repository, + Value? pluginApiVersion}) { + return PluginsTableCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + version: version ?? this.version, + author: author ?? this.author, + entryPoint: entryPoint ?? this.entryPoint, + apis: apis ?? this.apis, + abilities: abilities ?? this.abilities, + selectedForMetadata: selectedForMetadata ?? this.selectedForMetadata, + selectedForAudioSource: + selectedForAudioSource ?? this.selectedForAudioSource, + repository: repository ?? this.repository, + pluginApiVersion: pluginApiVersion ?? this.pluginApiVersion, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (version.present) { + map['version'] = Variable(version.value); + } + if (author.present) { + map['author'] = Variable(author.value); + } + if (entryPoint.present) { + map['entry_point'] = Variable(entryPoint.value); + } + if (apis.present) { + map['apis'] = Variable(apis.value); + } + if (abilities.present) { + map['abilities'] = Variable(abilities.value); + } + if (selectedForMetadata.present) { + map['selected_for_metadata'] = Variable(selectedForMetadata.value); + } + if (selectedForAudioSource.present) { + map['selected_for_audio_source'] = + Variable(selectedForAudioSource.value); + } + if (repository.present) { + map['repository'] = Variable(repository.value); + } + if (pluginApiVersion.present) { + map['plugin_api_version'] = Variable(pluginApiVersion.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PluginsTableCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('version: $version, ') + ..write('author: $author, ') + ..write('entryPoint: $entryPoint, ') + ..write('apis: $apis, ') + ..write('abilities: $abilities, ') + ..write('selectedForMetadata: $selectedForMetadata, ') + ..write('selectedForAudioSource: $selectedForAudioSource, ') + ..write('repository: $repository, ') + ..write('pluginApiVersion: $pluginApiVersion') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV10 extends GeneratedDatabase { + DatabaseAtV10(QueryExecutor e) : super(e); + late final AuthenticationTable authenticationTable = + AuthenticationTable(this); + late final BlacklistTable blacklistTable = BlacklistTable(this); + late final PreferencesTable preferencesTable = PreferencesTable(this); + late final ScrobblerTable scrobblerTable = ScrobblerTable(this); + late final SkipSegmentTable skipSegmentTable = SkipSegmentTable(this); + late final SourceMatchTable sourceMatchTable = SourceMatchTable(this); + late final AudioPlayerStateTable audioPlayerStateTable = + AudioPlayerStateTable(this); + late final HistoryTable historyTable = HistoryTable(this); + late final LyricsTable lyricsTable = LyricsTable(this); + late final PluginsTable pluginsTable = PluginsTable(this); + late final Index uniqueBlacklist = Index('unique_blacklist', + 'CREATE UNIQUE INDEX unique_blacklist ON blacklist_table (element_type, element_id)'); + late final Index uniqTrackMatch = Index('uniq_track_match', + 'CREATE UNIQUE INDEX uniq_track_match ON source_match_table (track_id, source_info, source_type)'); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + authenticationTable, + blacklistTable, + preferencesTable, + scrobblerTable, + skipSegmentTable, + sourceMatchTable, + audioPlayerStateTable, + historyTable, + lyricsTable, + pluginsTable, + uniqueBlacklist, + uniqTrackMatch + ]; + @override + int get schemaVersion => 10; +}