Initial commit
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
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');
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user