Initial commit
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
part 'database.g.dart';
|
||||
|
||||
/// Songs. Sync-fähig ab Tag 1: [id] ist eine stabile UUID, [updatedAtMs] und
|
||||
/// [deleted] (Tombstone) ermöglichen späteren Cloud-Sync ohne Schema-Umbau.
|
||||
class Songs extends Table {
|
||||
TextColumn get id => text()(); // uuid
|
||||
TextColumn get path => text().unique()();
|
||||
TextColumn get title => text()();
|
||||
TextColumn get artist => text().nullable()();
|
||||
TextColumn get album => text().nullable()();
|
||||
IntColumn get durationMs => integer().nullable()();
|
||||
TextColumn get coverPath => text().nullable()();
|
||||
IntColumn get dateAddedMs => integer()();
|
||||
IntColumn get updatedAtMs => integer()();
|
||||
BoolColumn get deleted => boolean().withDefault(const Constant(false))();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
/// Vom Nutzer gewählte Musikordner, die gescannt werden.
|
||||
class Folders extends Table {
|
||||
TextColumn get id => text()(); // uuid
|
||||
TextColumn get path => text().unique()();
|
||||
IntColumn get updatedAtMs => integer()();
|
||||
BoolColumn get deleted => boolean().withDefault(const Constant(false))();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
@DriftDatabase(tables: [Songs, Folders])
|
||||
class MeloDb extends _$MeloDb {
|
||||
MeloDb([QueryExecutor? executor]) : super(executor ?? _open());
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
|
||||
Stream<List<Song>> watchSongs() {
|
||||
return (select(songs)
|
||||
..where((s) => s.deleted.equals(false))
|
||||
..orderBy([(s) => OrderingTerm(expression: s.title)]))
|
||||
.watch();
|
||||
}
|
||||
|
||||
Stream<List<Song>> watchRecent({int limit = 50}) {
|
||||
return (select(songs)
|
||||
..where((s) => s.deleted.equals(false))
|
||||
..orderBy([
|
||||
(s) => OrderingTerm(expression: s.dateAddedMs, mode: OrderingMode.desc)
|
||||
])
|
||||
..limit(limit))
|
||||
.watch();
|
||||
}
|
||||
|
||||
Stream<List<Song>> searchSongs(String query) {
|
||||
final like = '%${query.toLowerCase()}%';
|
||||
return (select(songs)
|
||||
..where((s) =>
|
||||
s.deleted.equals(false) &
|
||||
(s.title.lower().like(like) |
|
||||
s.artist.lower().like(like) |
|
||||
s.album.lower().like(like)))
|
||||
..orderBy([(s) => OrderingTerm(expression: s.title)]))
|
||||
.watch();
|
||||
}
|
||||
|
||||
Future<List<Song>> allSongs() => select(songs).get();
|
||||
|
||||
Future<void> upsertSongs(List<SongsCompanion> items) async {
|
||||
await batch((b) => b.insertAllOnConflictUpdate(songs, items));
|
||||
}
|
||||
|
||||
/// Tombstone für Songs, deren Datei beim Scan nicht mehr gefunden wurde.
|
||||
Future<void> markMissing(List<String> livePaths, int now) async {
|
||||
final q = update(songs)..where((s) => s.deleted.equals(false));
|
||||
if (livePaths.isNotEmpty) {
|
||||
q.where((s) => s.path.isNotIn(livePaths));
|
||||
}
|
||||
await q.write(
|
||||
SongsCompanion(deleted: const Value(true), updatedAtMs: Value(now)),
|
||||
);
|
||||
}
|
||||
|
||||
Stream<List<Folder>> watchFolders() =>
|
||||
(select(folders)..where((f) => f.deleted.equals(false))).watch();
|
||||
|
||||
Future<List<Folder>> activeFolders() =>
|
||||
(select(folders)..where((f) => f.deleted.equals(false))).get();
|
||||
|
||||
Future<void> addFolder(FoldersCompanion folder) =>
|
||||
into(folders).insert(folder, onConflict: DoUpdate((_) => folder, target: [folders.path]));
|
||||
}
|
||||
|
||||
LazyDatabase _open() {
|
||||
return LazyDatabase(() async {
|
||||
final dir = await getApplicationSupportDirectory();
|
||||
final file = File(p.join(dir.path, 'melo.sqlite'));
|
||||
return NativeDatabase.createInBackground(file);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user