MusicDataSource refactored
This commit is contained in:
parent
d5ca1271a8
commit
178030b6ca
18 changed files with 384 additions and 353 deletions
|
@ -19,7 +19,7 @@ import 'system/audio/audio_player_impl.dart';
|
|||
import 'system/audio/queue_generator.dart';
|
||||
import 'system/datasources/local_music_fetcher.dart';
|
||||
import 'system/datasources/local_music_fetcher_contract.dart';
|
||||
import 'system/datasources/moor_music_data_source.dart';
|
||||
import 'system/datasources/moor_database.dart';
|
||||
import 'system/datasources/music_data_source_contract.dart';
|
||||
import 'system/datasources/player_state_data_source.dart';
|
||||
import 'system/datasources/settings_data_source.dart';
|
||||
|
@ -77,10 +77,10 @@ Future<void> setupGetIt() async {
|
|||
getIt.registerLazySingleton<SettingsRepository>(() => SettingsRepositoryImpl(getIt()));
|
||||
|
||||
// data sources
|
||||
final MoorMusicDataSource moorMusicDataSource = MoorMusicDataSource();
|
||||
getIt.registerLazySingleton<MusicDataSource>(() => moorMusicDataSource);
|
||||
getIt.registerLazySingleton<PlayerStateDataSource>(() => moorMusicDataSource.playerStateDao);
|
||||
getIt.registerLazySingleton<SettingsDataSource>(() => moorMusicDataSource.settingsDao);
|
||||
final MoorDatabase moorDatabase = MoorDatabase();
|
||||
getIt.registerLazySingleton<MusicDataSource>(() => moorDatabase.musicDataDao);
|
||||
getIt.registerLazySingleton<PlayerStateDataSource>(() => moorDatabase.playerStateDao);
|
||||
getIt.registerLazySingleton<SettingsDataSource>(() => moorDatabase.settingsDao);
|
||||
getIt.registerLazySingleton<LocalMusicFetcher>(
|
||||
() => LocalMusicFetcherImpl(
|
||||
getIt(),
|
||||
|
|
|
@ -23,7 +23,7 @@ class _HomePageState extends State<HomePage> {
|
|||
const Header(),
|
||||
const Highlight(),
|
||||
const ShuffleAllButton(
|
||||
verticalPad: 10.0,
|
||||
verticalPad: 20.0,
|
||||
horizontalPad: 0.0,
|
||||
),
|
||||
],
|
||||
|
|
173
lib/system/datasources/moor/music_data_dao.dart
Normal file
173
lib/system/datasources/moor/music_data_dao.dart
Normal file
|
@ -0,0 +1,173 @@
|
|||
import 'package:moor/moor.dart';
|
||||
|
||||
import '../../models/album_model.dart';
|
||||
import '../../models/artist_model.dart';
|
||||
import '../../models/song_model.dart';
|
||||
import '../moor_database.dart';
|
||||
import '../music_data_source_contract.dart';
|
||||
|
||||
part 'music_data_dao.g.dart';
|
||||
|
||||
@UseDao(tables: [Albums, Artists, Songs])
|
||||
class MusicDataDao extends DatabaseAccessor<MoorDatabase>
|
||||
with _$MusicDataDaoMixin
|
||||
implements MusicDataSource {
|
||||
MusicDataDao(MoorDatabase db) : super(db);
|
||||
|
||||
@override
|
||||
Future<List<AlbumModel>> getAlbums() async {
|
||||
return select(albums).get().then((moorAlbumList) =>
|
||||
moorAlbumList.map((moorAlbum) => AlbumModel.fromMoorAlbum(moorAlbum)).toList());
|
||||
}
|
||||
|
||||
// TODO: insert can throw exception -> implications?
|
||||
@override
|
||||
Future<int> insertAlbum(AlbumModel albumModel) async {
|
||||
return await into(albums).insert(albumModel.toAlbumsCompanion());
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<SongModel>> get songStream {
|
||||
return select(songs).watch().map((moorSongList) =>
|
||||
moorSongList.map((moorSong) => SongModel.fromMoorSong(moorSong)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<AlbumModel>> get albumStream {
|
||||
return select(albums).watch().map((moorAlbumList) =>
|
||||
moorAlbumList.map((moorAlbum) => AlbumModel.fromMoorAlbum(moorAlbum)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<ArtistModel>> get artistStream {
|
||||
return select(artists).watch().map((moorArtistList) =>
|
||||
moorArtistList.map((moorArtist) => ArtistModel.fromMoorArtist(moorArtist)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<SongModel>> getSongs() {
|
||||
return select(songs).get().then((moorSongList) =>
|
||||
moorSongList.map((moorSong) => SongModel.fromMoorSong(moorSong)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<SongModel>> getAlbumSongStream(AlbumModel album) {
|
||||
return (select(songs)
|
||||
..where((tbl) => tbl.albumId.equals(album.id))
|
||||
..orderBy([
|
||||
(t) => OrderingTerm(expression: t.discNumber),
|
||||
(t) => OrderingTerm(expression: t.trackNumber)
|
||||
]))
|
||||
.watch()
|
||||
.map((moorSongList) =>
|
||||
moorSongList.map((moorSong) => SongModel.fromMoorSong(moorSong)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<AlbumModel>> getArtistAlbumStream(ArtistModel artist) {
|
||||
return (select(albums)
|
||||
..where((tbl) => tbl.artist.equals(artist.name))
|
||||
..orderBy([
|
||||
(t) => OrderingTerm(expression: t.title),
|
||||
]))
|
||||
.watch()
|
||||
.map((moorAlbumList) =>
|
||||
moorAlbumList.map((moorAlbum) => AlbumModel.fromMoorAlbum(moorAlbum)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> insertSong(SongModel songModel) async {
|
||||
await into(songs).insert(songModel.toSongsCompanion());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<SongModel> getSongByPath(String path) async {
|
||||
return (select(songs)..where((t) => t.path.equals(path))).getSingle().then(
|
||||
(moorSong) {
|
||||
if (moorSong == null) {
|
||||
return null;
|
||||
}
|
||||
return SongModel.fromMoorSong(moorSong);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteAllArtists() async {
|
||||
delete(artists).go();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> insertArtist(ArtistModel artistModel) async {
|
||||
return into(artists).insert(artistModel.toArtistsCompanion());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteAllAlbums() async {
|
||||
delete(albums).go();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteAllSongs() async {
|
||||
delete(songs).go();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ArtistModel>> getArtists() {
|
||||
return select(artists).get().then((moorArtistList) =>
|
||||
moorArtistList.map((moorArtist) => ArtistModel.fromMoorArtist(moorArtist)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> insertSongs(List<SongModel> songModels) async {
|
||||
await update(songs).write(const SongsCompanion(present: Value(false)));
|
||||
|
||||
await batch((batch) {
|
||||
batch.insertAllOnConflictUpdate(
|
||||
songs,
|
||||
songModels.map((e) => e.toMoorInsert()).toList(),
|
||||
);
|
||||
});
|
||||
|
||||
await (delete(songs)..where((tbl) => tbl.present.equals(false))).go();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setSongBlocked(SongModel song, bool blocked) async {
|
||||
await (update(songs)..where((tbl) => tbl.path.equals(song.path)))
|
||||
.write(SongsCompanion(blocked: Value(blocked)));
|
||||
}
|
||||
|
||||
// EXPLORATORY CODE!!!
|
||||
@override
|
||||
Future<void> toggleNextSongLink(SongModel song) async {
|
||||
if (song.next == null) {
|
||||
final albumSongs = await (select(songs)
|
||||
..where((tbl) => tbl.albumId.equals(song.albumId))
|
||||
..orderBy([
|
||||
(t) => OrderingTerm(expression: t.discNumber),
|
||||
(t) => OrderingTerm(expression: t.trackNumber)
|
||||
]))
|
||||
.get()
|
||||
.then((moorSongList) =>
|
||||
moorSongList.map((moorSong) => SongModel.fromMoorSong(moorSong)).toList());
|
||||
|
||||
bool current = false;
|
||||
SongModel nextSong;
|
||||
for (final s in albumSongs) {
|
||||
if (current) {
|
||||
nextSong = s;
|
||||
break;
|
||||
}
|
||||
if (s.path == song.path) {
|
||||
current = true;
|
||||
}
|
||||
}
|
||||
await (update(songs)..where((tbl) => tbl.path.equals(song.path)))
|
||||
.write(SongsCompanion(next: Value(nextSong.path)));
|
||||
} else {
|
||||
await (update(songs)..where((tbl) => tbl.path.equals(song.path)))
|
||||
.write(const SongsCompanion(next: Value(null)));
|
||||
}
|
||||
}
|
||||
}
|
13
lib/system/datasources/moor/music_data_dao.g.dart
Normal file
13
lib/system/datasources/moor/music_data_dao.g.dart
Normal file
|
@ -0,0 +1,13 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'music_data_dao.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// DaoGenerator
|
||||
// **************************************************************************
|
||||
|
||||
mixin _$MusicDataDaoMixin on DatabaseAccessor<MoorDatabase> {
|
||||
$AlbumsTable get albums => attachedDatabase.albums;
|
||||
$ArtistsTable get artists => attachedDatabase.artists;
|
||||
$SongsTable get songs => attachedDatabase.songs;
|
||||
}
|
|
@ -6,16 +6,16 @@ import '../../models/loop_mode_model.dart';
|
|||
import '../../models/queue_item_model.dart';
|
||||
import '../../models/shuffle_mode_model.dart';
|
||||
import '../../models/song_model.dart';
|
||||
import '../moor_music_data_source.dart';
|
||||
import '../moor_database.dart';
|
||||
import '../player_state_data_source.dart';
|
||||
|
||||
part 'player_state_dao.g.dart';
|
||||
|
||||
@UseDao(tables: [Songs, QueueEntries, PersistentIndex, PersistentShuffleMode, PersistentLoopMode])
|
||||
class PlayerStateDao extends DatabaseAccessor<MoorMusicDataSource>
|
||||
class PlayerStateDao extends DatabaseAccessor<MoorDatabase>
|
||||
with _$PlayerStateDaoMixin
|
||||
implements PlayerStateDataSource {
|
||||
PlayerStateDao(MoorMusicDataSource db) : super(db);
|
||||
PlayerStateDao(MoorDatabase db) : super(db);
|
||||
|
||||
@override
|
||||
Stream<List<SongModel>> get songQueueStream {
|
||||
|
|
|
@ -6,7 +6,7 @@ part of 'player_state_dao.dart';
|
|||
// DaoGenerator
|
||||
// **************************************************************************
|
||||
|
||||
mixin _$PlayerStateDaoMixin on DatabaseAccessor<MoorMusicDataSource> {
|
||||
mixin _$PlayerStateDaoMixin on DatabaseAccessor<MoorDatabase> {
|
||||
$SongsTable get songs => attachedDatabase.songs;
|
||||
$QueueEntriesTable get queueEntries => attachedDatabase.queueEntries;
|
||||
$PersistentIndexTable get persistentIndex => attachedDatabase.persistentIndex;
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import 'package:moor/moor.dart';
|
||||
|
||||
import '../moor_music_data_source.dart';
|
||||
import '../moor_database.dart';
|
||||
import '../settings_data_source.dart';
|
||||
|
||||
part 'settings_dao.g.dart';
|
||||
|
||||
@UseDao(tables: [LibraryFolders])
|
||||
class SettingsDao extends DatabaseAccessor<MoorMusicDataSource>
|
||||
class SettingsDao extends DatabaseAccessor<MoorDatabase>
|
||||
with _$SettingsDaoMixin
|
||||
implements SettingsDataSource {
|
||||
SettingsDao(MoorMusicDataSource attachedDatabase) : super(attachedDatabase);
|
||||
SettingsDao(MoorDatabase attachedDatabase) : super(attachedDatabase);
|
||||
|
||||
@override
|
||||
Future<void> addLibraryFolder(String path) async {
|
||||
|
|
|
@ -6,6 +6,6 @@ part of 'settings_dao.dart';
|
|||
// DaoGenerator
|
||||
// **************************************************************************
|
||||
|
||||
mixin _$SettingsDaoMixin on DatabaseAccessor<MoorMusicDataSource> {
|
||||
mixin _$SettingsDaoMixin on DatabaseAccessor<MoorDatabase> {
|
||||
$LibraryFoldersTable get libraryFolders => attachedDatabase.libraryFolders;
|
||||
}
|
||||
|
|
166
lib/system/datasources/moor_database.dart
Normal file
166
lib/system/datasources/moor_database.dart
Normal file
|
@ -0,0 +1,166 @@
|
|||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:moor/ffi.dart';
|
||||
import 'package:moor/isolate.dart';
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'moor/music_data_dao.dart';
|
||||
import 'moor/player_state_dao.dart';
|
||||
import 'moor/settings_dao.dart';
|
||||
|
||||
part 'moor_database.g.dart';
|
||||
|
||||
const String MOOR_ISOLATE = 'MOOR_ISOLATE';
|
||||
|
||||
@DataClassName('MoorArtist')
|
||||
class Artists extends Table {
|
||||
TextColumn get name => text()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {name};
|
||||
}
|
||||
|
||||
@DataClassName('MoorAlbum')
|
||||
class Albums extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get title => text()();
|
||||
TextColumn get artist => text()();
|
||||
TextColumn get albumArtPath => text().nullable()();
|
||||
IntColumn get year => integer().nullable()();
|
||||
}
|
||||
|
||||
@DataClassName('MoorSong')
|
||||
class Songs extends Table {
|
||||
TextColumn get title => text()();
|
||||
TextColumn get albumTitle => text()();
|
||||
IntColumn get albumId => integer()();
|
||||
TextColumn get artist => text()();
|
||||
TextColumn get path => text()();
|
||||
IntColumn get duration => integer().nullable()();
|
||||
TextColumn get albumArtPath => text().nullable()();
|
||||
IntColumn get discNumber => integer().nullable()();
|
||||
IntColumn get trackNumber => integer().nullable()();
|
||||
BoolColumn get blocked => boolean().withDefault(const Constant(false))();
|
||||
BoolColumn get present => boolean().withDefault(const Constant(true))();
|
||||
|
||||
TextColumn get previous => text().nullable()();
|
||||
TextColumn get next => text().nullable()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {path};
|
||||
}
|
||||
|
||||
@DataClassName('MoorQueueEntry')
|
||||
class QueueEntries extends Table {
|
||||
IntColumn get index => integer()();
|
||||
TextColumn get path => text()();
|
||||
IntColumn get originalIndex => integer()();
|
||||
IntColumn get type => integer()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {index};
|
||||
}
|
||||
|
||||
class PersistentIndex extends Table {
|
||||
IntColumn get index => integer().nullable()();
|
||||
}
|
||||
|
||||
class PersistentShuffleMode extends Table {
|
||||
IntColumn get shuffleMode => integer().withDefault(const Constant(0))();
|
||||
}
|
||||
|
||||
class PersistentLoopMode extends Table {
|
||||
IntColumn get loopMode => integer().withDefault(const Constant(0))();
|
||||
}
|
||||
|
||||
class LibraryFolders extends Table {
|
||||
TextColumn get path => text()();
|
||||
}
|
||||
|
||||
@UseMoor(
|
||||
tables: [
|
||||
Albums,
|
||||
Artists,
|
||||
LibraryFolders,
|
||||
QueueEntries,
|
||||
PersistentIndex,
|
||||
PersistentLoopMode,
|
||||
PersistentShuffleMode,
|
||||
Songs,
|
||||
],
|
||||
daos: [
|
||||
PlayerStateDao,
|
||||
SettingsDao,
|
||||
MusicDataDao,
|
||||
],
|
||||
)
|
||||
class MoorDatabase extends _$MoorDatabase {
|
||||
/// Use MoorMusicDataSource in main isolate only.
|
||||
MoorDatabase() : super(_openConnection());
|
||||
|
||||
/// Used for testing with in-memory database.
|
||||
MoorDatabase.withQueryExecutor(QueryExecutor e) : super(e);
|
||||
|
||||
/// Used to connect to a database on another isolate.
|
||||
MoorDatabase.connect(DatabaseConnection connection) : super.connect(connection);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
|
||||
|
||||
}
|
||||
|
||||
LazyDatabase _openConnection() {
|
||||
// the LazyDatabase util lets us find the right location for the file async.
|
||||
return LazyDatabase(() async {
|
||||
// put the database file, called db.sqlite here, into the documents folder
|
||||
// for your app.
|
||||
final Directory dbFolder = await getApplicationDocumentsDirectory();
|
||||
final File file = File(p.join(dbFolder.path, 'db.sqlite'));
|
||||
return VmDatabase(file);
|
||||
});
|
||||
}
|
||||
|
||||
Future<MoorIsolate> createMoorIsolate() async {
|
||||
// this method is called from the main isolate. Since we can't use
|
||||
// getApplicationDocumentsDirectory on a background isolate, we calculate
|
||||
// the database path in the foreground isolate and then inform the
|
||||
// background isolate about the path.
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final path = p.join(dir.path, 'db.sqlite');
|
||||
final receivePort = ReceivePort();
|
||||
|
||||
await Isolate.spawn(
|
||||
_startBackground,
|
||||
_IsolateStartRequest(receivePort.sendPort, path),
|
||||
);
|
||||
|
||||
// _startBackground will send the MoorIsolate to this ReceivePort
|
||||
return await receivePort.first as MoorIsolate;
|
||||
}
|
||||
|
||||
void _startBackground(_IsolateStartRequest request) {
|
||||
// this is the entry point from the background isolate! Let's create
|
||||
// the database from the path we received
|
||||
final executor = VmDatabase(File(request.targetPath));
|
||||
// we're using MoorIsolate.inCurrent here as this method already runs on a
|
||||
// background isolate. If we used MoorIsolate.spawn, a third isolate would be
|
||||
// started which is not what we want!
|
||||
final moorIsolate = MoorIsolate.inCurrent(
|
||||
() => DatabaseConnection.fromExecutor(executor),
|
||||
);
|
||||
// inform the starting isolate about this, so that it can call .connect()
|
||||
request.sendMoorIsolate.send(moorIsolate);
|
||||
}
|
||||
|
||||
// used to bundle the SendPort and the target path, since isolate entry point
|
||||
// functions can only take one parameter.
|
||||
class _IsolateStartRequest {
|
||||
_IsolateStartRequest(this.sendMoorIsolate, this.targetPath);
|
||||
|
||||
final SendPort sendMoorIsolate;
|
||||
final String targetPath;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'moor_music_data_source.dart';
|
||||
part of 'moor_database.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// MoorGenerator
|
||||
|
@ -2130,10 +2130,9 @@ class $SongsTable extends Songs with TableInfo<$SongsTable, MoorSong> {
|
|||
}
|
||||
}
|
||||
|
||||
abstract class _$MoorMusicDataSource extends GeneratedDatabase {
|
||||
_$MoorMusicDataSource(QueryExecutor e)
|
||||
: super(SqlTypeSystem.defaultInstance, e);
|
||||
_$MoorMusicDataSource.connect(DatabaseConnection c) : super.connect(c);
|
||||
abstract class _$MoorDatabase extends GeneratedDatabase {
|
||||
_$MoorDatabase(QueryExecutor e) : super(SqlTypeSystem.defaultInstance, e);
|
||||
_$MoorDatabase.connect(DatabaseConnection c) : super.connect(c);
|
||||
$AlbumsTable _albums;
|
||||
$AlbumsTable get albums => _albums ??= $AlbumsTable(this);
|
||||
$ArtistsTable _artists;
|
||||
|
@ -2157,10 +2156,13 @@ abstract class _$MoorMusicDataSource extends GeneratedDatabase {
|
|||
$SongsTable get songs => _songs ??= $SongsTable(this);
|
||||
PlayerStateDao _playerStateDao;
|
||||
PlayerStateDao get playerStateDao =>
|
||||
_playerStateDao ??= PlayerStateDao(this as MoorMusicDataSource);
|
||||
_playerStateDao ??= PlayerStateDao(this as MoorDatabase);
|
||||
SettingsDao _settingsDao;
|
||||
SettingsDao get settingsDao =>
|
||||
_settingsDao ??= SettingsDao(this as MoorMusicDataSource);
|
||||
_settingsDao ??= SettingsDao(this as MoorDatabase);
|
||||
MusicDataDao _musicDataDao;
|
||||
MusicDataDao get musicDataDao =>
|
||||
_musicDataDao ??= MusicDataDao(this as MoorDatabase);
|
||||
@override
|
||||
Iterable<TableInfo> get allTables => allSchemaEntities.whereType<TableInfo>();
|
||||
@override
|
|
@ -1,323 +0,0 @@
|
|||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:moor/ffi.dart';
|
||||
import 'package:moor/isolate.dart';
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import '../models/album_model.dart';
|
||||
import '../models/artist_model.dart';
|
||||
import '../models/song_model.dart';
|
||||
import 'moor/player_state_dao.dart';
|
||||
import 'moor/settings_dao.dart';
|
||||
import 'music_data_source_contract.dart';
|
||||
|
||||
part 'moor_music_data_source.g.dart';
|
||||
|
||||
const String MOOR_ISOLATE = 'MOOR_ISOLATE';
|
||||
|
||||
@DataClassName('MoorArtist')
|
||||
class Artists extends Table {
|
||||
TextColumn get name => text()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {name};
|
||||
}
|
||||
|
||||
@DataClassName('MoorAlbum')
|
||||
class Albums extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get title => text()();
|
||||
TextColumn get artist => text()();
|
||||
TextColumn get albumArtPath => text().nullable()();
|
||||
IntColumn get year => integer().nullable()();
|
||||
}
|
||||
|
||||
@DataClassName('MoorSong')
|
||||
class Songs extends Table {
|
||||
TextColumn get title => text()();
|
||||
TextColumn get albumTitle => text()();
|
||||
IntColumn get albumId => integer()();
|
||||
TextColumn get artist => text()();
|
||||
TextColumn get path => text()();
|
||||
IntColumn get duration => integer().nullable()();
|
||||
TextColumn get albumArtPath => text().nullable()();
|
||||
IntColumn get discNumber => integer().nullable()();
|
||||
IntColumn get trackNumber => integer().nullable()();
|
||||
BoolColumn get blocked => boolean().withDefault(const Constant(false))();
|
||||
BoolColumn get present => boolean().withDefault(const Constant(true))();
|
||||
|
||||
TextColumn get previous => text().nullable()();
|
||||
TextColumn get next => text().nullable()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {path};
|
||||
}
|
||||
|
||||
@DataClassName('MoorQueueEntry')
|
||||
class QueueEntries extends Table {
|
||||
IntColumn get index => integer()();
|
||||
TextColumn get path => text()();
|
||||
IntColumn get originalIndex => integer()();
|
||||
IntColumn get type => integer()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {index};
|
||||
}
|
||||
|
||||
class PersistentIndex extends Table {
|
||||
IntColumn get index => integer().nullable()();
|
||||
}
|
||||
|
||||
class PersistentShuffleMode extends Table {
|
||||
IntColumn get shuffleMode => integer().withDefault(const Constant(0))();
|
||||
}
|
||||
|
||||
class PersistentLoopMode extends Table {
|
||||
IntColumn get loopMode => integer().withDefault(const Constant(0))();
|
||||
}
|
||||
|
||||
class LibraryFolders extends Table {
|
||||
TextColumn get path => text()();
|
||||
}
|
||||
|
||||
@UseMoor(
|
||||
tables: [
|
||||
Albums,
|
||||
Artists,
|
||||
LibraryFolders,
|
||||
QueueEntries,
|
||||
PersistentIndex,
|
||||
PersistentLoopMode,
|
||||
PersistentShuffleMode,
|
||||
Songs,
|
||||
],
|
||||
daos: [
|
||||
PlayerStateDao,
|
||||
SettingsDao,
|
||||
],
|
||||
)
|
||||
class MoorMusicDataSource extends _$MoorMusicDataSource implements MusicDataSource {
|
||||
/// Use MoorMusicDataSource in main isolate only.
|
||||
MoorMusicDataSource() : super(_openConnection());
|
||||
|
||||
/// Used for testing with in-memory database.
|
||||
MoorMusicDataSource.withQueryExecutor(QueryExecutor e) : super(e);
|
||||
|
||||
/// Used to connect to a database on another isolate.
|
||||
MoorMusicDataSource.connect(DatabaseConnection connection) : super.connect(connection);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
|
||||
@override
|
||||
Future<List<AlbumModel>> getAlbums() async {
|
||||
return select(albums).get().then((moorAlbumList) =>
|
||||
moorAlbumList.map((moorAlbum) => AlbumModel.fromMoorAlbum(moorAlbum)).toList());
|
||||
}
|
||||
|
||||
// TODO: insert can throw exception -> implications?
|
||||
@override
|
||||
Future<int> insertAlbum(AlbumModel albumModel) async {
|
||||
return await into(albums).insert(albumModel.toAlbumsCompanion());
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<SongModel>> get songStream {
|
||||
return select(songs).watch().map((moorSongList) =>
|
||||
moorSongList.map((moorSong) => SongModel.fromMoorSong(moorSong)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<AlbumModel>> get albumStream {
|
||||
return select(albums).watch().map((moorAlbumList) =>
|
||||
moorAlbumList.map((moorAlbum) => AlbumModel.fromMoorAlbum(moorAlbum)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<ArtistModel>> get artistStream {
|
||||
return select(artists).watch().map((moorArtistList) =>
|
||||
moorArtistList.map((moorArtist) => ArtistModel.fromMoorArtist(moorArtist)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<SongModel>> getSongs() {
|
||||
return select(songs).get().then((moorSongList) =>
|
||||
moorSongList.map((moorSong) => SongModel.fromMoorSong(moorSong)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<SongModel>> getAlbumSongStream(AlbumModel album) {
|
||||
return (select(songs)
|
||||
..where((tbl) => tbl.albumId.equals(album.id))
|
||||
..orderBy([
|
||||
(t) => OrderingTerm(expression: t.discNumber),
|
||||
(t) => OrderingTerm(expression: t.trackNumber)
|
||||
]))
|
||||
.watch()
|
||||
.map((moorSongList) =>
|
||||
moorSongList.map((moorSong) => SongModel.fromMoorSong(moorSong)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<AlbumModel>> getArtistAlbumStream(ArtistModel artist) {
|
||||
return (select(albums)
|
||||
..where((tbl) => tbl.artist.equals(artist.name))
|
||||
..orderBy([
|
||||
(t) => OrderingTerm(expression: t.title),
|
||||
]))
|
||||
.watch()
|
||||
.map((moorAlbumList) =>
|
||||
moorAlbumList.map((moorAlbum) => AlbumModel.fromMoorAlbum(moorAlbum)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> insertSong(SongModel songModel) async {
|
||||
await into(songs).insert(songModel.toSongsCompanion());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<SongModel> getSongByPath(String path) async {
|
||||
return (select(songs)..where((t) => t.path.equals(path))).getSingle().then(
|
||||
(moorSong) {
|
||||
if (moorSong == null) {
|
||||
return null;
|
||||
}
|
||||
return SongModel.fromMoorSong(moorSong);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteAllArtists() async {
|
||||
delete(artists).go();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> insertArtist(ArtistModel artistModel) async {
|
||||
return into(artists).insert(artistModel.toArtistsCompanion());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteAllAlbums() async {
|
||||
delete(albums).go();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteAllSongs() async {
|
||||
delete(songs).go();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ArtistModel>> getArtists() {
|
||||
return select(artists).get().then((moorArtistList) =>
|
||||
moorArtistList.map((moorArtist) => ArtistModel.fromMoorArtist(moorArtist)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> insertSongs(List<SongModel> songModels) async {
|
||||
await update(songs).write(const SongsCompanion(present: Value(false)));
|
||||
|
||||
await batch((batch) {
|
||||
batch.insertAllOnConflictUpdate(
|
||||
songs,
|
||||
songModels.map((e) => e.toMoorInsert()).toList(),
|
||||
);
|
||||
});
|
||||
|
||||
await (delete(songs)..where((tbl) => tbl.present.equals(false))).go();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setSongBlocked(SongModel song, bool blocked) async {
|
||||
await (update(songs)..where((tbl) => tbl.path.equals(song.path)))
|
||||
.write(SongsCompanion(blocked: Value(blocked)));
|
||||
}
|
||||
|
||||
// EXPLORATORY CODE!!!
|
||||
@override
|
||||
Future<void> toggleNextSongLink(SongModel song) async {
|
||||
if (song.next == null) {
|
||||
final albumSongs = await (select(songs)
|
||||
..where((tbl) => tbl.albumId.equals(song.albumId))
|
||||
..orderBy([
|
||||
(t) => OrderingTerm(expression: t.discNumber),
|
||||
(t) => OrderingTerm(expression: t.trackNumber)
|
||||
]))
|
||||
.get()
|
||||
.then((moorSongList) =>
|
||||
moorSongList.map((moorSong) => SongModel.fromMoorSong(moorSong)).toList());
|
||||
|
||||
bool current = false;
|
||||
SongModel nextSong;
|
||||
for (final s in albumSongs) {
|
||||
if (current) {
|
||||
nextSong = s;
|
||||
break;
|
||||
}
|
||||
if (s.path == song.path) {
|
||||
current = true;
|
||||
}
|
||||
}
|
||||
await (update(songs)..where((tbl) => tbl.path.equals(song.path)))
|
||||
.write(SongsCompanion(next: Value(nextSong.path)));
|
||||
} else {
|
||||
await (update(songs)..where((tbl) => tbl.path.equals(song.path)))
|
||||
.write(const SongsCompanion(next: Value(null)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyDatabase _openConnection() {
|
||||
// the LazyDatabase util lets us find the right location for the file async.
|
||||
return LazyDatabase(() async {
|
||||
// put the database file, called db.sqlite here, into the documents folder
|
||||
// for your app.
|
||||
final Directory dbFolder = await getApplicationDocumentsDirectory();
|
||||
final File file = File(p.join(dbFolder.path, 'db.sqlite'));
|
||||
return VmDatabase(file);
|
||||
});
|
||||
}
|
||||
|
||||
Future<MoorIsolate> createMoorIsolate() async {
|
||||
// this method is called from the main isolate. Since we can't use
|
||||
// getApplicationDocumentsDirectory on a background isolate, we calculate
|
||||
// the database path in the foreground isolate and then inform the
|
||||
// background isolate about the path.
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final path = p.join(dir.path, 'db.sqlite');
|
||||
final receivePort = ReceivePort();
|
||||
|
||||
await Isolate.spawn(
|
||||
_startBackground,
|
||||
_IsolateStartRequest(receivePort.sendPort, path),
|
||||
);
|
||||
|
||||
// _startBackground will send the MoorIsolate to this ReceivePort
|
||||
return await receivePort.first as MoorIsolate;
|
||||
}
|
||||
|
||||
void _startBackground(_IsolateStartRequest request) {
|
||||
// this is the entry point from the background isolate! Let's create
|
||||
// the database from the path we received
|
||||
final executor = VmDatabase(File(request.targetPath));
|
||||
// we're using MoorIsolate.inCurrent here as this method already runs on a
|
||||
// background isolate. If we used MoorIsolate.spawn, a third isolate would be
|
||||
// started which is not what we want!
|
||||
final moorIsolate = MoorIsolate.inCurrent(
|
||||
() => DatabaseConnection.fromExecutor(executor),
|
||||
);
|
||||
// inform the starting isolate about this, so that it can call .connect()
|
||||
request.sendMoorIsolate.send(moorIsolate);
|
||||
}
|
||||
|
||||
// used to bundle the SendPort and the target path, since isolate entry point
|
||||
// functions can only take one parameter.
|
||||
class _IsolateStartRequest {
|
||||
_IsolateStartRequest(this.sendMoorIsolate, this.targetPath);
|
||||
|
||||
final SendPort sendMoorIsolate;
|
||||
final String targetPath;
|
||||
}
|
|
@ -3,7 +3,7 @@ import 'package:meta/meta.dart';
|
|||
import 'package:moor/moor.dart';
|
||||
|
||||
import '../../domain/entities/album.dart';
|
||||
import '../datasources/moor_music_data_source.dart';
|
||||
import '../datasources/moor_database.dart';
|
||||
|
||||
class AlbumModel extends Album {
|
||||
const AlbumModel({
|
||||
|
|
|
@ -3,7 +3,7 @@ import 'package:meta/meta.dart';
|
|||
import 'package:moor/moor.dart';
|
||||
|
||||
import '../../domain/entities/artist.dart';
|
||||
import '../datasources/moor_music_data_source.dart';
|
||||
import '../datasources/moor_database.dart';
|
||||
|
||||
class ArtistModel extends Artist {
|
||||
const ArtistModel({
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'package:meta/meta.dart';
|
|||
import 'package:moor/moor.dart';
|
||||
|
||||
import '../../domain/entities/song.dart';
|
||||
import '../datasources/moor_music_data_source.dart';
|
||||
import '../datasources/moor_database.dart';
|
||||
|
||||
class SongModel extends Song {
|
||||
const SongModel({
|
||||
|
|
|
@ -7,7 +7,7 @@ import 'package:mucke/system/audio/audio_manager_contract.dart';
|
|||
import 'package:mucke/system/datasources/local_music_fetcher.dart';
|
||||
import 'package:mucke/system/datasources/local_music_fetcher_contract.dart';
|
||||
import 'package:mucke/system/audio/audio_manager.dart';
|
||||
import 'package:mucke/system/datasources/moor_music_data_source.dart';
|
||||
import 'package:mucke/system/datasources/moor_database.dart';
|
||||
import 'package:mucke/system/audio/queue_generator.dart';
|
||||
import 'package:mucke/system/datasources/music_data_source_contract.dart';
|
||||
import 'package:mucke/system/models/artist_model.dart';
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:moor/ffi.dart';
|
||||
import 'package:mucke/system/datasources/moor_music_data_source.dart';
|
||||
import 'package:mucke/system/datasources/moor_database.dart';
|
||||
import 'package:mucke/system/models/album_model.dart';
|
||||
import 'package:mucke/system/models/song_model.dart';
|
||||
|
||||
import '../../test_constants.dart';
|
||||
|
||||
void main() {
|
||||
MoorMusicDataSource moorMusicDataSource;
|
||||
MoorDatabase moorMusicDataSource;
|
||||
AlbumModel albumModel;
|
||||
SongModel songModel;
|
||||
|
||||
setUp(() {
|
||||
moorMusicDataSource =
|
||||
MoorMusicDataSource.withQueryExecutor(VmDatabase.memory());
|
||||
MoorDatabase.withQueryExecutor(VmDatabase.memory());
|
||||
|
||||
albumModel = const AlbumModel(
|
||||
title: ALBUM_TITLE_1,
|
||||
|
|
|
@ -3,7 +3,7 @@ import 'package:flutter_test/flutter_test.dart';
|
|||
import 'package:mockito/mockito.dart';
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:mucke/domain/entities/album.dart';
|
||||
import 'package:mucke/system/datasources/moor_music_data_source.dart';
|
||||
import 'package:mucke/system/datasources/moor_database.dart';
|
||||
import 'package:mucke/system/models/album_model.dart';
|
||||
|
||||
import '../../test_constants.dart';
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'package:flutter_test/flutter_test.dart';
|
|||
import 'package:mockito/mockito.dart';
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:mucke/domain/entities/song.dart';
|
||||
import 'package:mucke/system/datasources/moor_music_data_source.dart';
|
||||
import 'package:mucke/system/datasources/moor_database.dart';
|
||||
import 'package:mucke/system/models/song_model.dart';
|
||||
|
||||
import '../../test_constants.dart';
|
||||
|
|
Loading…
Add table
Reference in a new issue