238 lines
7.6 KiB
Dart
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');
|
|
});
|
|
}
|
|
}
|