✨ Impl more features (clean up 20+ todo)
⚡ Add query cache
This commit is contained in:
@ -21,7 +21,6 @@ import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
|
||||
part 'database.g.dart';
|
||||
|
||||
part 'tables/authentication.dart';
|
||||
part 'tables/blacklist.dart';
|
||||
part 'tables/preferences.dart';
|
||||
part 'tables/scrobbler.dart';
|
||||
part 'tables/skip_segment.dart';
|
||||
@ -39,7 +38,6 @@ part 'typeconverters/subtitle.dart';
|
||||
@DriftDatabase(
|
||||
tables: [
|
||||
AuthenticationTable,
|
||||
BlacklistTable,
|
||||
PreferencesTable,
|
||||
ScrobblerTable,
|
||||
SkipSegmentTable,
|
||||
|
@ -281,276 +281,6 @@ class AuthenticationTableCompanion
|
||||
}
|
||||
}
|
||||
|
||||
class $BlacklistTableTable extends BlacklistTable
|
||||
with TableInfo<$BlacklistTableTable, BlacklistTableData> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$BlacklistTableTable(this.attachedDatabase, [this._alias]);
|
||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
@override
|
||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||
'id', aliasedName, false,
|
||||
hasAutoIncrement: true,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints:
|
||||
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
|
||||
static const VerificationMeta _nameMeta = const VerificationMeta('name');
|
||||
@override
|
||||
late final GeneratedColumn<String> name = GeneratedColumn<String>(
|
||||
'name', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||
static const VerificationMeta _elementTypeMeta =
|
||||
const VerificationMeta('elementType');
|
||||
@override
|
||||
late final GeneratedColumnWithTypeConverter<BlacklistedType, String>
|
||||
elementType = GeneratedColumn<String>('element_type', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true)
|
||||
.withConverter<BlacklistedType>(
|
||||
$BlacklistTableTable.$converterelementType);
|
||||
static const VerificationMeta _elementIdMeta =
|
||||
const VerificationMeta('elementId');
|
||||
@override
|
||||
late final GeneratedColumn<String> elementId = GeneratedColumn<String>(
|
||||
'element_id', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [id, name, elementType, elementId];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'blacklist_table';
|
||||
@override
|
||||
VerificationContext validateIntegrity(Insertable<BlacklistTableData> instance,
|
||||
{bool isInserting = false}) {
|
||||
final context = VerificationContext();
|
||||
final data = instance.toColumns(true);
|
||||
if (data.containsKey('id')) {
|
||||
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
|
||||
}
|
||||
if (data.containsKey('name')) {
|
||||
context.handle(
|
||||
_nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_nameMeta);
|
||||
}
|
||||
context.handle(_elementTypeMeta, const VerificationResult.success());
|
||||
if (data.containsKey('element_id')) {
|
||||
context.handle(_elementIdMeta,
|
||||
elementId.isAcceptableOrUnknown(data['element_id']!, _elementIdMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_elementIdMeta);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
BlacklistTableData map(Map<String, dynamic> 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: $BlacklistTableTable.$converterelementType.fromSql(
|
||||
attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string, data['${effectivePrefix}element_type'])!),
|
||||
elementId: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}element_id'])!,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
$BlacklistTableTable createAlias(String alias) {
|
||||
return $BlacklistTableTable(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
static JsonTypeConverter2<BlacklistedType, String, String>
|
||||
$converterelementType =
|
||||
const EnumNameConverter<BlacklistedType>(BlacklistedType.values);
|
||||
}
|
||||
|
||||
class BlacklistTableData extends DataClass
|
||||
implements Insertable<BlacklistTableData> {
|
||||
final int id;
|
||||
final String name;
|
||||
final BlacklistedType elementType;
|
||||
final String elementId;
|
||||
const BlacklistTableData(
|
||||
{required this.id,
|
||||
required this.name,
|
||||
required this.elementType,
|
||||
required this.elementId});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['id'] = Variable<int>(id);
|
||||
map['name'] = Variable<String>(name);
|
||||
{
|
||||
map['element_type'] = Variable<String>(
|
||||
$BlacklistTableTable.$converterelementType.toSql(elementType));
|
||||
}
|
||||
map['element_id'] = Variable<String>(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<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return BlacklistTableData(
|
||||
id: serializer.fromJson<int>(json['id']),
|
||||
name: serializer.fromJson<String>(json['name']),
|
||||
elementType: $BlacklistTableTable.$converterelementType
|
||||
.fromJson(serializer.fromJson<String>(json['elementType'])),
|
||||
elementId: serializer.fromJson<String>(json['elementId']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<int>(id),
|
||||
'name': serializer.toJson<String>(name),
|
||||
'elementType': serializer.toJson<String>(
|
||||
$BlacklistTableTable.$converterelementType.toJson(elementType)),
|
||||
'elementId': serializer.toJson<String>(elementId),
|
||||
};
|
||||
}
|
||||
|
||||
BlacklistTableData copyWith(
|
||||
{int? id,
|
||||
String? name,
|
||||
BlacklistedType? 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<BlacklistTableData> {
|
||||
final Value<int> id;
|
||||
final Value<String> name;
|
||||
final Value<BlacklistedType> elementType;
|
||||
final Value<String> 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 BlacklistedType elementType,
|
||||
required String elementId,
|
||||
}) : name = Value(name),
|
||||
elementType = Value(elementType),
|
||||
elementId = Value(elementId);
|
||||
static Insertable<BlacklistTableData> custom({
|
||||
Expression<int>? id,
|
||||
Expression<String>? name,
|
||||
Expression<String>? elementType,
|
||||
Expression<String>? 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<int>? id,
|
||||
Value<String>? name,
|
||||
Value<BlacklistedType>? elementType,
|
||||
Value<String>? elementId}) {
|
||||
return BlacklistTableCompanion(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
elementType: elementType ?? this.elementType,
|
||||
elementId: elementId ?? this.elementId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (id.present) {
|
||||
map['id'] = Variable<int>(id.value);
|
||||
}
|
||||
if (name.present) {
|
||||
map['name'] = Variable<String>(name.value);
|
||||
}
|
||||
if (elementType.present) {
|
||||
map['element_type'] = Variable<String>(
|
||||
$BlacklistTableTable.$converterelementType.toSql(elementType.value));
|
||||
}
|
||||
if (elementId.present) {
|
||||
map['element_id'] = Variable<String>(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 $PreferencesTableTable extends PreferencesTable
|
||||
with TableInfo<$PreferencesTableTable, PreferencesTableData> {
|
||||
@override
|
||||
@ -3226,7 +2956,6 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
||||
$AppDatabaseManager get managers => $AppDatabaseManager(this);
|
||||
late final $AuthenticationTableTable authenticationTable =
|
||||
$AuthenticationTableTable(this);
|
||||
late final $BlacklistTableTable blacklistTable = $BlacklistTableTable(this);
|
||||
late final $PreferencesTableTable preferencesTable =
|
||||
$PreferencesTableTable(this);
|
||||
late final $ScrobblerTableTable scrobblerTable = $ScrobblerTableTable(this);
|
||||
@ -3236,8 +2965,6 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
||||
$SourceMatchTableTable(this);
|
||||
late final $HistoryTableTable historyTable = $HistoryTableTable(this);
|
||||
late final $LyricsTableTable lyricsTable = $LyricsTableTable(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_id, source_type)');
|
||||
@override
|
||||
@ -3246,14 +2973,12 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities => [
|
||||
authenticationTable,
|
||||
blacklistTable,
|
||||
preferencesTable,
|
||||
scrobblerTable,
|
||||
skipSegmentTable,
|
||||
sourceMatchTable,
|
||||
historyTable,
|
||||
lyricsTable,
|
||||
uniqueBlacklist,
|
||||
uniqTrackMatch
|
||||
];
|
||||
}
|
||||
@ -3395,139 +3120,6 @@ typedef $$AuthenticationTableTableProcessedTableManager = ProcessedTableManager<
|
||||
),
|
||||
AuthenticationTableData,
|
||||
PrefetchHooks Function()>;
|
||||
typedef $$BlacklistTableTableCreateCompanionBuilder = BlacklistTableCompanion
|
||||
Function({
|
||||
Value<int> id,
|
||||
required String name,
|
||||
required BlacklistedType elementType,
|
||||
required String elementId,
|
||||
});
|
||||
typedef $$BlacklistTableTableUpdateCompanionBuilder = BlacklistTableCompanion
|
||||
Function({
|
||||
Value<int> id,
|
||||
Value<String> name,
|
||||
Value<BlacklistedType> elementType,
|
||||
Value<String> elementId,
|
||||
});
|
||||
|
||||
class $$BlacklistTableTableFilterComposer
|
||||
extends FilterComposer<_$AppDatabase, $BlacklistTableTable> {
|
||||
$$BlacklistTableTableFilterComposer(super.$state);
|
||||
ColumnFilters<int> get id => $state.composableBuilder(
|
||||
column: $state.table.id,
|
||||
builder: (column, joinBuilders) =>
|
||||
ColumnFilters(column, joinBuilders: joinBuilders));
|
||||
|
||||
ColumnFilters<String> get name => $state.composableBuilder(
|
||||
column: $state.table.name,
|
||||
builder: (column, joinBuilders) =>
|
||||
ColumnFilters(column, joinBuilders: joinBuilders));
|
||||
|
||||
ColumnWithTypeConverterFilters<BlacklistedType, BlacklistedType, String>
|
||||
get elementType => $state.composableBuilder(
|
||||
column: $state.table.elementType,
|
||||
builder: (column, joinBuilders) => ColumnWithTypeConverterFilters(
|
||||
column,
|
||||
joinBuilders: joinBuilders));
|
||||
|
||||
ColumnFilters<String> get elementId => $state.composableBuilder(
|
||||
column: $state.table.elementId,
|
||||
builder: (column, joinBuilders) =>
|
||||
ColumnFilters(column, joinBuilders: joinBuilders));
|
||||
}
|
||||
|
||||
class $$BlacklistTableTableOrderingComposer
|
||||
extends OrderingComposer<_$AppDatabase, $BlacklistTableTable> {
|
||||
$$BlacklistTableTableOrderingComposer(super.$state);
|
||||
ColumnOrderings<int> get id => $state.composableBuilder(
|
||||
column: $state.table.id,
|
||||
builder: (column, joinBuilders) =>
|
||||
ColumnOrderings(column, joinBuilders: joinBuilders));
|
||||
|
||||
ColumnOrderings<String> get name => $state.composableBuilder(
|
||||
column: $state.table.name,
|
||||
builder: (column, joinBuilders) =>
|
||||
ColumnOrderings(column, joinBuilders: joinBuilders));
|
||||
|
||||
ColumnOrderings<String> get elementType => $state.composableBuilder(
|
||||
column: $state.table.elementType,
|
||||
builder: (column, joinBuilders) =>
|
||||
ColumnOrderings(column, joinBuilders: joinBuilders));
|
||||
|
||||
ColumnOrderings<String> get elementId => $state.composableBuilder(
|
||||
column: $state.table.elementId,
|
||||
builder: (column, joinBuilders) =>
|
||||
ColumnOrderings(column, joinBuilders: joinBuilders));
|
||||
}
|
||||
|
||||
class $$BlacklistTableTableTableManager extends RootTableManager<
|
||||
_$AppDatabase,
|
||||
$BlacklistTableTable,
|
||||
BlacklistTableData,
|
||||
$$BlacklistTableTableFilterComposer,
|
||||
$$BlacklistTableTableOrderingComposer,
|
||||
$$BlacklistTableTableCreateCompanionBuilder,
|
||||
$$BlacklistTableTableUpdateCompanionBuilder,
|
||||
(
|
||||
BlacklistTableData,
|
||||
BaseReferences<_$AppDatabase, $BlacklistTableTable, BlacklistTableData>
|
||||
),
|
||||
BlacklistTableData,
|
||||
PrefetchHooks Function()> {
|
||||
$$BlacklistTableTableTableManager(
|
||||
_$AppDatabase db, $BlacklistTableTable table)
|
||||
: super(TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
filteringComposer:
|
||||
$$BlacklistTableTableFilterComposer(ComposerState(db, table)),
|
||||
orderingComposer:
|
||||
$$BlacklistTableTableOrderingComposer(ComposerState(db, table)),
|
||||
updateCompanionCallback: ({
|
||||
Value<int> id = const Value.absent(),
|
||||
Value<String> name = const Value.absent(),
|
||||
Value<BlacklistedType> elementType = const Value.absent(),
|
||||
Value<String> elementId = const Value.absent(),
|
||||
}) =>
|
||||
BlacklistTableCompanion(
|
||||
id: id,
|
||||
name: name,
|
||||
elementType: elementType,
|
||||
elementId: elementId,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
Value<int> id = const Value.absent(),
|
||||
required String name,
|
||||
required BlacklistedType elementType,
|
||||
required String elementId,
|
||||
}) =>
|
||||
BlacklistTableCompanion.insert(
|
||||
id: id,
|
||||
name: name,
|
||||
elementType: elementType,
|
||||
elementId: elementId,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: null,
|
||||
));
|
||||
}
|
||||
|
||||
typedef $$BlacklistTableTableProcessedTableManager = ProcessedTableManager<
|
||||
_$AppDatabase,
|
||||
$BlacklistTableTable,
|
||||
BlacklistTableData,
|
||||
$$BlacklistTableTableFilterComposer,
|
||||
$$BlacklistTableTableOrderingComposer,
|
||||
$$BlacklistTableTableCreateCompanionBuilder,
|
||||
$$BlacklistTableTableUpdateCompanionBuilder,
|
||||
(
|
||||
BlacklistTableData,
|
||||
BaseReferences<_$AppDatabase, $BlacklistTableTable, BlacklistTableData>
|
||||
),
|
||||
BlacklistTableData,
|
||||
PrefetchHooks Function()>;
|
||||
typedef $$PreferencesTableTableCreateCompanionBuilder
|
||||
= PreferencesTableCompanion Function({
|
||||
Value<int> id,
|
||||
@ -4727,8 +4319,6 @@ class $AppDatabaseManager {
|
||||
$AppDatabaseManager(this._db);
|
||||
$$AuthenticationTableTableTableManager get authenticationTable =>
|
||||
$$AuthenticationTableTableTableManager(_db, _db.authenticationTable);
|
||||
$$BlacklistTableTableTableManager get blacklistTable =>
|
||||
$$BlacklistTableTableTableManager(_db, _db.blacklistTable);
|
||||
$$PreferencesTableTableTableManager get preferencesTable =>
|
||||
$$PreferencesTableTableTableManager(_db, _db.preferencesTable);
|
||||
$$ScrobblerTableTableTableManager get scrobblerTable =>
|
||||
|
@ -1,18 +0,0 @@
|
||||
part of '../database.dart';
|
||||
|
||||
enum BlacklistedType {
|
||||
artist,
|
||||
track;
|
||||
}
|
||||
|
||||
@TableIndex(
|
||||
name: "unique_blacklist",
|
||||
unique: true,
|
||||
columns: {#elementType, #elementId},
|
||||
)
|
||||
class BlacklistTable extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get name => text()();
|
||||
TextColumn get elementType => textEnum<BlacklistedType>()();
|
||||
TextColumn get elementId => text()();
|
||||
}
|
@ -13,8 +13,7 @@ enum CloseBehavior {
|
||||
|
||||
enum AudioSource {
|
||||
youtube,
|
||||
piped,
|
||||
jiosaavn;
|
||||
piped;
|
||||
|
||||
String get label => name[0].toUpperCase() + name.substring(1);
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
part of '../database.dart';
|
||||
|
||||
enum SourceType {
|
||||
youtube._("YouTube"),
|
||||
youtubeMusic._("YouTube Music"),
|
||||
jiosaavn._("JioSaavn");
|
||||
youtube._('YouTube'),
|
||||
youtubeMusic._('YouTube Music');
|
||||
|
||||
final String label;
|
||||
|
||||
@ -11,7 +10,7 @@ enum SourceType {
|
||||
}
|
||||
|
||||
@TableIndex(
|
||||
name: "uniq_track_match",
|
||||
name: 'uniq_track_match',
|
||||
columns: {#trackId, #sourceId, #sourceType},
|
||||
unique: true,
|
||||
)
|
||||
|
@ -6,14 +6,14 @@ class LocaleConverter extends TypeConverter<Locale, String> {
|
||||
@override
|
||||
Locale fromSql(String fromDb) {
|
||||
final rawMap = jsonDecode(fromDb) as Map<String, dynamic>;
|
||||
return Locale(rawMap["languageCode"], rawMap["countryCode"]);
|
||||
return Locale(rawMap['languageCode'], rawMap['countryCode']);
|
||||
}
|
||||
|
||||
@override
|
||||
String toSql(Locale value) {
|
||||
return jsonEncode({
|
||||
"languageCode": value.languageCode,
|
||||
"countryCode": value.countryCode,
|
||||
'languageCode': value.languageCode,
|
||||
'countryCode': value.countryCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,11 @@ class StringListConverter extends TypeConverter<List<String>, String> {
|
||||
|
||||
@override
|
||||
List<String> fromSql(String fromDb) {
|
||||
return fromDb.split(",").where((e) => e.isNotEmpty).toList();
|
||||
return fromDb.split(',').where((e) => e.isNotEmpty).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
String toSql(List<String> value) {
|
||||
return value.join(",");
|
||||
return value.join(',');
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
enum SearchMode {
|
||||
youtube._('YouTube'),
|
||||
youtubeMusic._('YouTube Music');
|
||||
|
||||
final String label;
|
||||
|
||||
const SearchMode._(this.label);
|
||||
|
||||
factory SearchMode.fromString(String key) {
|
||||
return SearchMode.values.firstWhere((e) => e.name == key);
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import 'package:piped_client/piped_client.dart';
|
||||
import 'package:rhythm_box/services/sourced_track/models/search.dart';
|
||||
|
||||
import 'package:rhythm_box/services/database/database.dart';
|
||||
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||
|
||||
class YoutubeVideoInfo {
|
||||
|
@ -1,4 +1,7 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rhythm_box/providers/user_preferences.dart';
|
||||
import 'package:rhythm_box/services/database/database.dart';
|
||||
import 'package:rhythm_box/services/utils.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
|
||||
@ -40,8 +43,8 @@ abstract class SourcedTrack extends Track {
|
||||
}
|
||||
|
||||
static SourcedTrack fromJson(Map<String, dynamic> json) {
|
||||
// TODO Follow user preferences
|
||||
const audioSource = 'youtube';
|
||||
final preferences = Get.find<UserPreferencesProvider>().state.value;
|
||||
final audioSource = preferences.audioSource;
|
||||
|
||||
final sourceInfo = SourceInfo.fromJson(json);
|
||||
final source = SourceMap.fromJson(json);
|
||||
@ -52,7 +55,7 @@ abstract class SourcedTrack extends Track {
|
||||
.cast<SourceInfo>();
|
||||
|
||||
return switch (audioSource) {
|
||||
'piped' => PipedSourcedTrack(
|
||||
AudioSource.piped => PipedSourcedTrack(
|
||||
source: source,
|
||||
siblings: siblings,
|
||||
sourceInfo: sourceInfo,
|
||||
@ -86,12 +89,13 @@ abstract class SourcedTrack extends Track {
|
||||
static Future<SourcedTrack> fetchFromTrack({
|
||||
required Track track,
|
||||
}) async {
|
||||
// TODO Follow user preferences
|
||||
const audioSource = 'youtube';
|
||||
final preferences = Get.find<UserPreferencesProvider>().state.value;
|
||||
final audioSource = preferences.audioSource;
|
||||
|
||||
try {
|
||||
return switch (audioSource) {
|
||||
'piped' => await PipedSourcedTrack.fetchFromTrack(track: track),
|
||||
AudioSource.piped =>
|
||||
await PipedSourcedTrack.fetchFromTrack(track: track),
|
||||
_ => await YoutubeSourcedTrack.fetchFromTrack(track: track),
|
||||
};
|
||||
} on TrackNotFoundError catch (_) {
|
||||
@ -110,11 +114,11 @@ abstract class SourcedTrack extends Track {
|
||||
static Future<List<SiblingType>> fetchSiblings({
|
||||
required Track track,
|
||||
}) {
|
||||
// TODO Follow user preferences
|
||||
const audioSource = 'youtube';
|
||||
final preferences = Get.find<UserPreferencesProvider>().state.value;
|
||||
final audioSource = preferences.audioSource;
|
||||
|
||||
return switch (audioSource) {
|
||||
'piped' => PipedSourcedTrack.fetchSiblings(track: track),
|
||||
AudioSource.piped => PipedSourcedTrack.fetchSiblings(track: track),
|
||||
_ => YoutubeSourcedTrack.fetchSiblings(track: track),
|
||||
};
|
||||
}
|
||||
@ -128,15 +132,15 @@ abstract class SourcedTrack extends Track {
|
||||
}
|
||||
|
||||
String get url {
|
||||
// TODO Follow user preferences
|
||||
const streamMusicCodec = SourceCodecs.weba;
|
||||
final preferences = Get.find<UserPreferencesProvider>().state.value;
|
||||
final streamMusicCodec = preferences.streamMusicCodec;
|
||||
|
||||
return getUrlOfCodec(streamMusicCodec);
|
||||
}
|
||||
|
||||
String getUrlOfCodec(SourceCodecs codec) {
|
||||
// TODO Follow user preferences
|
||||
const audioQuality = SourceQualities.high;
|
||||
final preferences = Get.find<UserPreferencesProvider>().state.value;
|
||||
final audioQuality = preferences.audioQuality;
|
||||
|
||||
return source[codec]?[audioQuality] ??
|
||||
// this will ensure playback doesn't break
|
||||
@ -145,8 +149,8 @@ abstract class SourcedTrack extends Track {
|
||||
}
|
||||
|
||||
SourceCodecs get codec {
|
||||
// TODO Follow user preferences
|
||||
const streamMusicCodec = SourceCodecs.weba;
|
||||
final preferences = Get.find<UserPreferencesProvider>().state.value;
|
||||
final streamMusicCodec = preferences.streamMusicCodec;
|
||||
|
||||
return streamMusicCodec;
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:get/get.dart' hide Value;
|
||||
import 'package:piped_client/piped_client.dart';
|
||||
import 'package:rhythm_box/services/sourced_track/models/search.dart';
|
||||
import 'package:rhythm_box/providers/database.dart';
|
||||
import 'package:rhythm_box/providers/user_preferences.dart';
|
||||
import 'package:rhythm_box/services/database/database.dart';
|
||||
import 'package:rhythm_box/services/utils.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
|
||||
@ -41,21 +45,62 @@ class PipedSourcedTrack extends SourcedTrack {
|
||||
static Future<SourcedTrack> fetchFromTrack({
|
||||
required Track track,
|
||||
}) async {
|
||||
// TODO Add cache query here
|
||||
final DatabaseProvider db = Get.find();
|
||||
final cachedSource = await (db.database.select(db.database.sourceMatchTable)
|
||||
..where((s) => s.trackId.equals(track.id!))
|
||||
..limit(1)
|
||||
..orderBy([
|
||||
(s) =>
|
||||
OrderingTerm(expression: s.createdAt, mode: OrderingMode.desc),
|
||||
]))
|
||||
.getSingleOrNull();
|
||||
|
||||
final siblings = await fetchSiblings(track: track);
|
||||
if (siblings.isEmpty) {
|
||||
throw TrackNotFoundError(track);
|
||||
final preferences = Get.find<UserPreferencesProvider>().state.value;
|
||||
|
||||
if (cachedSource == null) {
|
||||
final siblings = await fetchSiblings(track: track);
|
||||
if (siblings.isEmpty) {
|
||||
throw TrackNotFoundError(track);
|
||||
}
|
||||
|
||||
await db.database.into(db.database.sourceMatchTable).insert(
|
||||
SourceMatchTableCompanion.insert(
|
||||
trackId: track.id!,
|
||||
sourceId: siblings.first.info.id,
|
||||
sourceType: Value(
|
||||
preferences.searchMode == SearchMode.youtube
|
||||
? SourceType.youtube
|
||||
: SourceType.youtubeMusic,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return PipedSourcedTrack(
|
||||
siblings: siblings.map((s) => s.info).skip(1).toList(),
|
||||
source: siblings.first.source as SourceMap,
|
||||
sourceInfo: siblings.first.info,
|
||||
track: track,
|
||||
);
|
||||
} else {
|
||||
final client = _getClient();
|
||||
final manifest = await client.streams(cachedSource.sourceId);
|
||||
|
||||
return PipedSourcedTrack(
|
||||
siblings: [],
|
||||
source: toSourceMap(manifest),
|
||||
sourceInfo: PipedSourceInfo(
|
||||
id: manifest.id,
|
||||
artist: manifest.uploader,
|
||||
artistUrl: manifest.uploaderUrl,
|
||||
pageUrl: 'https://www.youtube.com/watch?v=${manifest.id}',
|
||||
thumbnail: manifest.thumbnailUrl,
|
||||
title: manifest.title,
|
||||
duration: manifest.duration,
|
||||
album: null,
|
||||
),
|
||||
track: track,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO Insert to cache here
|
||||
|
||||
return PipedSourcedTrack(
|
||||
siblings: siblings.map((s) => s.info).skip(1).toList(),
|
||||
source: siblings.first.source as SourceMap,
|
||||
sourceInfo: siblings.first.info,
|
||||
track: track,
|
||||
);
|
||||
}
|
||||
|
||||
static SourceMap toSourceMap(PipedStreamResponse manifest) {
|
||||
@ -114,11 +159,10 @@ class PipedSourcedTrack extends SourcedTrack {
|
||||
required Track track,
|
||||
}) async {
|
||||
final pipedClient = _getClient();
|
||||
final preferences = Get.find<UserPreferencesProvider>().state.value;
|
||||
|
||||
// TODO Allow user search with normal youtube video (`youtube`)
|
||||
const searchMode = SearchMode.youtubeMusic;
|
||||
// TODO Follow user preferences
|
||||
const audioSource = 'youtube';
|
||||
final searchMode = preferences.searchMode;
|
||||
final audioSource = preferences.audioSource;
|
||||
|
||||
final query = SourcedTrack.getSearchTerm(track);
|
||||
|
||||
@ -130,8 +174,9 @@ class PipedSourcedTrack extends SourcedTrack {
|
||||
);
|
||||
|
||||
// when falling back to piped API make sure to use the YouTube mode
|
||||
const isYouTubeMusic =
|
||||
audioSource != 'piped' ? false : searchMode == SearchMode.youtubeMusic;
|
||||
final isYouTubeMusic = audioSource != AudioSource.piped
|
||||
? false
|
||||
: searchMode == SearchMode.youtubeMusic;
|
||||
|
||||
if (isYouTubeMusic) {
|
||||
final artists = (track.artists ?? [])
|
||||
@ -227,7 +272,18 @@ class PipedSourcedTrack extends SourcedTrack {
|
||||
|
||||
final manifest = await pipedClient.streams(newSourceInfo.id);
|
||||
|
||||
// TODO Save to cache here
|
||||
final DatabaseProvider db = Get.find();
|
||||
await db.database.into(db.database.sourceMatchTable).insert(
|
||||
SourceMatchTableCompanion.insert(
|
||||
trackId: 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(
|
||||
siblings: newSiblings,
|
||||
|
@ -1,7 +1,11 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:get/get.dart' hide Value;
|
||||
import 'package:http/http.dart';
|
||||
import 'package:rhythm_box/providers/database.dart';
|
||||
import 'package:rhythm_box/services/database/database.dart';
|
||||
import 'package:rhythm_box/services/utils.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:rhythm_box/services/song_link/song_link.dart';
|
||||
@ -43,19 +47,61 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
static Future<YoutubeSourcedTrack> fetchFromTrack({
|
||||
required Track track,
|
||||
}) async {
|
||||
// TODO Add cache query here
|
||||
final DatabaseProvider db = Get.find();
|
||||
final cachedSource = await (db.database.select(db.database.sourceMatchTable)
|
||||
..where((s) => s.trackId.equals(track.id!))
|
||||
..limit(1)
|
||||
..orderBy([
|
||||
(s) =>
|
||||
OrderingTerm(expression: s.createdAt, mode: OrderingMode.desc),
|
||||
]))
|
||||
.get()
|
||||
.then((s) => s.firstOrNull);
|
||||
|
||||
final siblings = await fetchSiblings(track: track);
|
||||
if (siblings.isEmpty) {
|
||||
throw TrackNotFoundError(track);
|
||||
if (cachedSource == null || cachedSource.sourceType != SourceType.youtube) {
|
||||
final siblings = await fetchSiblings(track: track);
|
||||
if (siblings.isEmpty) {
|
||||
throw TrackNotFoundError(track);
|
||||
}
|
||||
|
||||
await db.database.into(db.database.sourceMatchTable).insert(
|
||||
SourceMatchTableCompanion.insert(
|
||||
trackId: track.id!,
|
||||
sourceId: siblings.first.info.id,
|
||||
sourceType: const Value(SourceType.youtube),
|
||||
),
|
||||
);
|
||||
|
||||
return YoutubeSourcedTrack(
|
||||
siblings: siblings.map((s) => s.info).skip(1).toList(),
|
||||
source: siblings.first.source as SourceMap,
|
||||
sourceInfo: siblings.first.info,
|
||||
track: track,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO Save to cache here
|
||||
|
||||
final item = await youtubeClient.videos.get(cachedSource.sourceId);
|
||||
final manifest = await youtubeClient.videos.streamsClient
|
||||
.getManifest(
|
||||
cachedSource.sourceId,
|
||||
)
|
||||
.timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () => throw ClientException('Timeout'),
|
||||
);
|
||||
return YoutubeSourcedTrack(
|
||||
siblings: siblings.map((s) => s.info).skip(1).toList(),
|
||||
source: siblings.first.source as SourceMap,
|
||||
sourceInfo: siblings.first.info,
|
||||
siblings: [],
|
||||
source: toSourceMap(manifest),
|
||||
sourceInfo: YoutubeSourceInfo(
|
||||
id: item.id.value,
|
||||
artist: item.author,
|
||||
artistUrl: 'https://www.youtube.com/channel/${item.channelId}',
|
||||
pageUrl: item.url,
|
||||
thumbnail: item.thumbnails.highResUrl,
|
||||
title: item.title,
|
||||
duration: item.duration ?? Duration.zero,
|
||||
album: null,
|
||||
),
|
||||
track: track,
|
||||
);
|
||||
}
|
||||
@ -243,7 +289,19 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
onTimeout: () => throw ClientException('Timeout'),
|
||||
);
|
||||
|
||||
// TODO Save to cache here
|
||||
final DatabaseProvider db = Get.find();
|
||||
|
||||
await db.database.into(db.database.sourceMatchTable).insert(
|
||||
SourceMatchTableCompanion.insert(
|
||||
trackId: 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(
|
||||
siblings: newSiblings,
|
||||
|
Reference in New Issue
Block a user