Files
melo-app/lib/database/db_helper.dart
T

238 lines
7.6 KiB
Dart

import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart' as p;
import '../models/song.dart';
import '../models/tag.dart';
class DbHelper {
static final DbHelper _instanz = DbHelper._();
factory DbHelper() => _instanz;
DbHelper._();
Database? _db;
Future<Database> get db async {
if (_db != null) return _db!;
_db = await _init();
return _db!;
}
Future<Database> _init() async {
final pfad = await getDatabasesPath();
return openDatabase(
p.join(pfad, 'melo.db'),
version: 1,
onCreate: (db, version) async {
await db.execute('''
CREATE TABLE songs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
titel TEXT NOT NULL,
kuenstler TEXT NOT NULL,
album TEXT DEFAULT '',
dauer_sekunden INTEGER NOT NULL,
datei_pfad TEXT NOT NULL UNIQUE,
cover_pfad TEXT,
groesse_bytes INTEGER DEFAULT 0,
ist_heruntergeladen INTEGER DEFAULT 0,
hinzugefuegt_am TEXT NOT NULL,
download_quelle TEXT DEFAULT 'local',
zuletzt_position INTEGER
)
''');
await db.execute('''
CREATE TABLE tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
icon TEXT,
farbe_hex TEXT
)
''');
await db.execute('''
CREATE TABLE song_tags (
song_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
PRIMARY KEY (song_id, tag_id),
FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
)
''');
await db.execute('''
CREATE TABLE playlists (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
erstellt_am TEXT NOT NULL
)
''');
await db.execute('''
CREATE TABLE playlist_songs (
playlist_id INTEGER NOT NULL,
song_id INTEGER NOT NULL,
position INTEGER NOT NULL,
PRIMARY KEY (playlist_id, song_id),
FOREIGN KEY (playlist_id) REFERENCES playlists(id) ON DELETE CASCADE,
FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE CASCADE
)
''');
await db.execute('''
CREATE TABLE wiedergabe_verlauf (
id INTEGER PRIMARY KEY AUTOINCREMENT,
song_id INTEGER NOT NULL,
position INTEGER DEFAULT 0,
zuletzt_abgespielt TEXT NOT NULL,
FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE CASCADE
)
''');
},
);
}
// ─── Songs ──────────────────────────────────────
Future<int> songEinfuegen(Song song) async {
final d = await db;
return d.insert('songs', song.toMap(),
conflictAlgorithm: ConflictAlgorithm.ignore);
}
Future<void> songsEinfuegen(List<Song> songs) async {
final d = await db;
final batch = d.batch();
for (final song in songs) {
batch.insert('songs', song.toMap(),
conflictAlgorithm: ConflictAlgorithm.ignore);
}
await batch.commit(noResult: true);
}
Future<List<Song>> alleSongs() async {
final d = await db;
final rows = await d.query('songs', orderBy: 'hinzugefuegt_am DESC');
return rows.map((r) => Song.fromMap(r)).toList();
}
Future<Song?> songNachId(int id) async {
final d = await db;
final rows = await d.query('songs', where: 'id = ?', whereArgs: [id]);
if (rows.isEmpty) return null;
return Song.fromMap(rows.first);
}
Future<Song?> songNachPfad(String pfad) async {
final d = await db;
final rows = await d.query('songs', where: 'datei_pfad = ?', whereArgs: [pfad]);
if (rows.isEmpty) return null;
return Song.fromMap(rows.first);
}
Future<void> positionAktualisieren(int songId, int position) async {
final d = await db;
await d.update('songs', {'zuletzt_position': position},
where: 'id = ?', whereArgs: [songId]);
final rows = await d.update('wiedergabe_verlauf',
{'position': position, 'zuletzt_abgespielt': DateTime.now().toIso8601String()},
where: 'song_id = ?', whereArgs: [songId]);
if (rows == 0) {
await d.insert('wiedergabe_verlauf', {
'song_id': songId,
'position': position,
'zuletzt_abgespielt': DateTime.now().toIso8601String(),
});
}
}
// ─── Tags ────────────────────────────────────────
Future<int> tagErstellen(String name, {String? icon, String? farbe}) async {
final d = await db;
return d.insert('tags', {
'name': name, 'icon': icon, 'farbe_hex': farbe,
}, conflictAlgorithm: ConflictAlgorithm.ignore);
}
Future<List<Tag>> alleTags() async {
final d = await db;
final rows = await d.query('tags', orderBy: 'name');
return rows.map((r) => Tag.fromMap(r)).toList();
}
Future<void> songTagHinzufuegen(int songId, int tagId) async {
final d = await db;
await d.insert('song_tags', {'song_id': songId, 'tag_id': tagId},
conflictAlgorithm: ConflictAlgorithm.ignore);
}
Future<List<Tag>> tagsFuerSong(int songId) async {
final d = await db;
final rows = await d.rawQuery('''
SELECT t.* FROM tags t
JOIN song_tags st ON t.id = st.tag_id
WHERE st.song_id = ?
''', [songId]);
return rows.map((r) => Tag.fromMap(r)).toList();
}
// ─── Playlists ───────────────────────────────────
Future<int> playlistErstellen(String name) async {
final d = await db;
return d.insert('playlists', {
'name': name,
'erstellt_am': DateTime.now().toIso8601String(),
});
}
Future<void> songZurPlaylist(int playlistId, int songId, int position) async {
final d = await db;
await d.insert('playlist_songs', {
'playlist_id': playlistId,
'song_id': songId,
'position': position,
});
}
Future<List<Map<String, dynamic>>> allePlaylists() async {
final d = await db;
return d.query('playlists', orderBy: 'erstellt_am DESC');
}
Future<void> songAusPlaylistEntfernen(int playlistId, int songId) async {
final d = await db;
await d.delete('playlist_songs',
where: 'playlist_id = ? AND song_id = ?',
whereArgs: [playlistId, songId]);
}
Future<void> metadatenAktualisieren(int songId, {String? titel, String? kuenstler, String? album}) async {
final d = await db;
final update = <String, dynamic>{};
if (titel != null) update['titel'] = titel;
if (kuenstler != null) update['kuenstler'] = kuenstler;
if (album != null) update['album'] = album;
if (update.isNotEmpty) {
await d.update('songs', update, where: 'id = ?', whereArgs: [songId]);
}
}
Future<List<Song>> songsDerPlaylist(int playlistId) async {
final d = await db;
final rows = await d.rawQuery('''
SELECT s.* FROM songs s
JOIN playlist_songs ps ON s.id = ps.song_id
WHERE ps.playlist_id = ?
ORDER BY ps.position
''', [playlistId]);
return rows.map((r) => Song.fromMap(r)).toList();
}
Future<void> loeschen() async {
final d = await db;
await d.transaction((txn) async {
await txn.delete('wiedergabe_verlauf');
await txn.delete('song_tags');
await txn.delete('playlist_songs');
await txn.delete('playlists');
await txn.delete('tags');
await txn.delete('songs');
});
}
}