引入播放列表库,同时引入全新的播放控制器,以控制读库与播放模式的动作,但是功能仍在完善,需要确认部分边界问题
This commit is contained in:
+240
-14
@@ -21,16 +21,57 @@ class SongDatabase {
|
||||
final path = join(dir.path, 'qingting_songs.db');
|
||||
return await openDatabase(
|
||||
path,
|
||||
version: 1,
|
||||
version: 2,
|
||||
onCreate: _onCreate,
|
||||
onUpgrade: _onUpgrade,
|
||||
);
|
||||
}
|
||||
|
||||
// ⭐ 新增:检查表是否存在
|
||||
Future<bool> _tableExists(Database db, String tableName) async {
|
||||
final result = await db.query(
|
||||
'sqlite_master',
|
||||
where: 'type = ? AND name = ?',
|
||||
whereArgs: ['table', tableName],
|
||||
);
|
||||
return result.isNotEmpty;
|
||||
}
|
||||
|
||||
Future<void> _onCreate(Database db, int version) async {
|
||||
await _createSongsTable(db);
|
||||
await _createMetadataCacheTable(db);
|
||||
await _createPlaylistsTable(db);
|
||||
await _createPlaylistSongsTable(db);
|
||||
await _createFavoritesTable(db);
|
||||
await _createIndexes(db);
|
||||
}
|
||||
|
||||
// ⭐ 升级逻辑(从版本 1 升级到 2)
|
||||
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
|
||||
if (oldVersion < 2) {
|
||||
// 检查 playlists 表是否存在
|
||||
final playlistsExists = await _tableExists(db, 'playlists');
|
||||
|
||||
if (!playlistsExists) {
|
||||
// 表不存在 → 创建所有新表
|
||||
await _createPlaylistsTable(db);
|
||||
await _createPlaylistSongsTable(db);
|
||||
await _createFavoritesTable(db);
|
||||
await _createIndexes(db);
|
||||
} else {
|
||||
// 表已存在 → 只添加新字段
|
||||
await db.execute(
|
||||
'ALTER TABLE playlists ADD COLUMN play_mode TEXT DEFAULT "sequential"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _createSongsTable(Database db) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE songs (
|
||||
song_key TEXT PRIMARY KEY,
|
||||
remote_path TEXT,
|
||||
content_hash TEXT,
|
||||
file_size INTEGER,
|
||||
modified_time INTEGER,
|
||||
etag TEXT,
|
||||
@@ -46,7 +87,9 @@ class SongDatabase {
|
||||
metadata_status TEXT DEFAULT 'pending'
|
||||
)
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> _createMetadataCacheTable(Database db) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE metadata_cache (
|
||||
song_key TEXT PRIMARY KEY,
|
||||
@@ -56,12 +99,59 @@ class SongDatabase {
|
||||
FOREIGN KEY (song_key) REFERENCES songs(song_key) ON DELETE CASCADE
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('CREATE INDEX idx_songs_artist ON songs(artist)');
|
||||
await db.execute('CREATE INDEX idx_songs_title ON songs(title)');
|
||||
}
|
||||
|
||||
// ---- 查询 ----
|
||||
Future<void> _createPlaylistsTable(Database db) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE playlists (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
play_mode TEXT DEFAULT 'sequential',
|
||||
created_at INTEGER,
|
||||
updated_at INTEGER
|
||||
)
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> _createPlaylistSongsTable(Database db) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE playlist_songs (
|
||||
playlist_id TEXT,
|
||||
song_id TEXT,
|
||||
order_index INTEGER,
|
||||
added_at INTEGER,
|
||||
PRIMARY KEY (playlist_id, song_id),
|
||||
FOREIGN KEY (playlist_id) REFERENCES playlists(id) ON DELETE CASCADE
|
||||
)
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> _createFavoritesTable(Database db) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE favorites (
|
||||
song_id TEXT PRIMARY KEY,
|
||||
favorited_at INTEGER,
|
||||
FOREIGN KEY (song_id) REFERENCES songs(remote_path) ON DELETE CASCADE
|
||||
)
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> _createIndexes(Database db) async {
|
||||
await db.execute(
|
||||
'CREATE INDEX IF NOT EXISTS idx_songs_artist ON songs(artist)');
|
||||
await db
|
||||
.execute('CREATE INDEX IF NOT EXISTS idx_songs_title ON songs(title)');
|
||||
await db.execute(
|
||||
'CREATE INDEX IF NOT EXISTS idx_songs_content_hash ON songs(content_hash)');
|
||||
await db.execute(
|
||||
'CREATE INDEX IF NOT EXISTS idx_playlist_songs_playlist ON playlist_songs(playlist_id)');
|
||||
await db.execute(
|
||||
'CREATE INDEX IF NOT EXISTS idx_playlist_songs_order ON playlist_songs(order_index)');
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 查询方法(保持不变)
|
||||
// ════════════════════════════════════════════════════════════
|
||||
Future<Map<String, dynamic>?> getSong(String songKey) async {
|
||||
final db = await database;
|
||||
final result =
|
||||
@@ -69,6 +159,20 @@ class SongDatabase {
|
||||
return result.isNotEmpty ? result.first : null;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> getSongByPath(String remotePath) async {
|
||||
final db = await database;
|
||||
final result = await db
|
||||
.query('songs', where: 'remote_path = ?', whereArgs: [remotePath]);
|
||||
return result.isNotEmpty ? result.first : null;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> getSongByHash(String contentHash) async {
|
||||
final db = await database;
|
||||
final result = await db
|
||||
.query('songs', where: 'content_hash = ?', whereArgs: [contentHash]);
|
||||
return result.isNotEmpty ? result.first : null;
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getSongsByArtist(String artist,
|
||||
{int limit = 20}) async {
|
||||
if (artist.isEmpty) return [];
|
||||
@@ -81,7 +185,135 @@ class SongDatabase {
|
||||
);
|
||||
}
|
||||
|
||||
// ---- 写入 ----
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 播放列表 CRUD(保持不变)
|
||||
// ════════════════════════════════════════════════════════════
|
||||
Future<List<Map<String, dynamic>>> getAllPlaylists() async {
|
||||
final db = await database;
|
||||
return await db.query('playlists', orderBy: 'created_at DESC');
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> getPlaylist(String id) async {
|
||||
final db = await database;
|
||||
final result =
|
||||
await db.query('playlists', where: 'id = ?', whereArgs: [id]);
|
||||
return result.isNotEmpty ? result.first : null;
|
||||
}
|
||||
|
||||
Future<void> insertPlaylist(Map<String, dynamic> playlist) async {
|
||||
final db = await database;
|
||||
await db.insert('playlists', playlist,
|
||||
conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
}
|
||||
|
||||
Future<void> updatePlaylist(String id, Map<String, dynamic> updates) async {
|
||||
final db = await database;
|
||||
await db.update('playlists', updates, where: 'id = ?', whereArgs: [id]);
|
||||
}
|
||||
|
||||
Future<void> deletePlaylist(String id) async {
|
||||
final db = await database;
|
||||
await db.delete('playlists', where: 'id = ?', whereArgs: [id]);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 播放列表歌曲操作(保持不变)
|
||||
// ════════════════════════════════════════════════════════════
|
||||
Future<List<Map<String, dynamic>>> getPlaylistSongs(String playlistId) async {
|
||||
final db = await database;
|
||||
return await db.query(
|
||||
'playlist_songs',
|
||||
where: 'playlist_id = ?',
|
||||
whereArgs: [playlistId],
|
||||
orderBy: 'order_index ASC',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> addSongToPlaylist(String playlistId, String songId,
|
||||
{int? orderIndex}) async {
|
||||
final db = await database;
|
||||
int finalOrder = orderIndex ?? 0;
|
||||
if (orderIndex == null) {
|
||||
final existing = await db.query(
|
||||
'playlist_songs',
|
||||
where: 'playlist_id = ?',
|
||||
whereArgs: [playlistId],
|
||||
orderBy: 'order_index DESC',
|
||||
limit: 1,
|
||||
);
|
||||
finalOrder =
|
||||
existing.isNotEmpty ? (existing.first['order_index'] as int) + 1 : 0;
|
||||
}
|
||||
await db.insert(
|
||||
'playlist_songs',
|
||||
{
|
||||
'playlist_id': playlistId,
|
||||
'song_id': songId,
|
||||
'order_index': finalOrder,
|
||||
'added_at': DateTime.now().millisecondsSinceEpoch,
|
||||
},
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> removeSongFromPlaylist(String playlistId, String songId) async {
|
||||
final db = await database;
|
||||
await db.delete(
|
||||
'playlist_songs',
|
||||
where: 'playlist_id = ? AND song_id = ?',
|
||||
whereArgs: [playlistId, songId],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> clearPlaylist(String playlistId) async {
|
||||
final db = await database;
|
||||
await db.delete('playlist_songs',
|
||||
where: 'playlist_id = ?', whereArgs: [playlistId]);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 收藏操作(保持不变)
|
||||
// ════════════════════════════════════════════════════════════
|
||||
Future<List<Map<String, dynamic>>> getFavorites() async {
|
||||
final db = await database;
|
||||
return await db.query('favorites', orderBy: 'favorited_at DESC');
|
||||
}
|
||||
|
||||
Future<bool> isFavorite(String songId) async {
|
||||
final db = await database;
|
||||
final result =
|
||||
await db.query('favorites', where: 'song_id = ?', whereArgs: [songId]);
|
||||
return result.isNotEmpty;
|
||||
}
|
||||
|
||||
Future<void> addFavorite(String songId) async {
|
||||
final db = await database;
|
||||
await db.insert(
|
||||
'favorites',
|
||||
{
|
||||
'song_id': songId,
|
||||
'favorited_at': DateTime.now().millisecondsSinceEpoch,
|
||||
},
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> removeFavorite(String songId) async {
|
||||
final db = await database;
|
||||
await db.delete('favorites', where: 'song_id = ?', whereArgs: [songId]);
|
||||
}
|
||||
|
||||
Future<void> toggleFavorite(String songId) async {
|
||||
if (await isFavorite(songId)) {
|
||||
await removeFavorite(songId);
|
||||
} else {
|
||||
await addFavorite(songId);
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 原有写入/删除方法(保持不变)
|
||||
// ════════════════════════════════════════════════════════════
|
||||
Future<void> insertSong(Map<String, dynamic> song) async {
|
||||
final db = await database;
|
||||
await db.insert('songs', song,
|
||||
@@ -90,15 +322,10 @@ class SongDatabase {
|
||||
|
||||
Future<void> updateSong(String songKey, Map<String, dynamic> updates) async {
|
||||
final db = await database;
|
||||
await db.update(
|
||||
'songs',
|
||||
updates,
|
||||
where: 'song_key = ?',
|
||||
whereArgs: [songKey],
|
||||
);
|
||||
await db
|
||||
.update('songs', updates, where: 'song_key = ?', whereArgs: [songKey]);
|
||||
}
|
||||
|
||||
// ---- 删除 ----
|
||||
Future<void> deleteSong(String songKey) async {
|
||||
final db = await database;
|
||||
await db.delete('songs', where: 'song_key = ?', whereArgs: [songKey]);
|
||||
@@ -110,7 +337,6 @@ class SongDatabase {
|
||||
.delete('metadata_cache', where: 'song_key = ?', whereArgs: [songKey]);
|
||||
}
|
||||
|
||||
// ---- 缓存 ----
|
||||
Future<void> insertCache(Map<String, dynamic> cache) async {
|
||||
final db = await database;
|
||||
await db.insert('metadata_cache', cache,
|
||||
|
||||
Reference in New Issue
Block a user