mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-23 19:32:29 +02:00
refactor: clean up redundant comments and code
This commit is contained in:
@@ -1034,14 +1034,12 @@ void removeItem(String id) {
|
||||
}
|
||||
|
||||
try {
|
||||
// Get base download directory
|
||||
String baseDir = state.outputDir;
|
||||
if (baseDir.isEmpty) {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
baseDir = dir.path;
|
||||
}
|
||||
|
||||
// Create failed_downloads subfolder
|
||||
final failedDownloadsDir = '$baseDir/failed_downloads';
|
||||
final failedDir = Directory(failedDownloadsDir);
|
||||
if (!await failedDir.exists()) {
|
||||
@@ -1057,11 +1055,9 @@ void removeItem(String id) {
|
||||
final file = File(filePath);
|
||||
final bool fileExists = await file.exists();
|
||||
|
||||
// Build content for new entries
|
||||
final buffer = StringBuffer();
|
||||
|
||||
if (!fileExists) {
|
||||
// New file - add header
|
||||
buffer.writeln('# SpotiFLAC Failed Downloads');
|
||||
buffer.writeln('# Date: $dateStr');
|
||||
buffer.writeln('#');
|
||||
@@ -1069,7 +1065,6 @@ void removeItem(String id) {
|
||||
buffer.writeln('');
|
||||
}
|
||||
|
||||
// Add timestamp for this batch
|
||||
final timeStr = '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}:${now.second.toString().padLeft(2, '0')}';
|
||||
|
||||
for (final item in failedItems) {
|
||||
@@ -1081,7 +1076,6 @@ void removeItem(String id) {
|
||||
buffer.writeln('[$timeStr] ${track.name} - ${track.artistName} | $spotifyUrl | $error');
|
||||
}
|
||||
|
||||
// Append or create file
|
||||
if (fileExists) {
|
||||
await file.writeAsString(buffer.toString(), mode: FileMode.append);
|
||||
_log.i('Appended ${failedItems.length} failed downloads to: $filePath');
|
||||
@@ -1553,7 +1547,6 @@ void removeItem(String id) {
|
||||
|
||||
_log.d('Opus Metadata map content: $metadata');
|
||||
|
||||
// Handle lyrics based on lyricsMode setting
|
||||
final lyricsMode = settings.lyricsMode;
|
||||
final shouldEmbed = lyricsMode == 'embed' || lyricsMode == 'both';
|
||||
final shouldSaveExternal = lyricsMode == 'external' || lyricsMode == 'both';
|
||||
@@ -1571,13 +1564,11 @@ void removeItem(String id) {
|
||||
);
|
||||
|
||||
if (lrcContent.isNotEmpty) {
|
||||
// Embed lyrics in file metadata if mode is 'embed' or 'both'
|
||||
if (shouldEmbed) {
|
||||
metadata['LYRICS'] = lrcContent;
|
||||
_log.d('Lyrics fetched for Opus embedding (${lrcContent.length} chars)');
|
||||
}
|
||||
|
||||
// Save external LRC file if mode is 'external' or 'both'
|
||||
if (shouldSaveExternal) {
|
||||
try {
|
||||
final lrcPath = opusPath.replaceAll(RegExp(r'\.opus$', caseSensitive: false), '.lrc');
|
||||
@@ -2137,7 +2128,6 @@ result = await PlatformBridge.downloadWithExtensions(
|
||||
progress: 0.95,
|
||||
);
|
||||
|
||||
// Convert M4A to the selected format
|
||||
final format = tidalHighFormat.startsWith('opus') ? 'opus' : 'mp3';
|
||||
final convertedPath = await FFmpegService.convertM4aToLossy(
|
||||
filePath,
|
||||
@@ -2154,7 +2144,6 @@ result = await PlatformBridge.downloadWithExtensions(
|
||||
actualQuality = '${format.toUpperCase()} $bitrateDisplay';
|
||||
_log.i('Successfully converted M4A to $format: $convertedPath');
|
||||
|
||||
// Embed metadata
|
||||
_log.i('Embedding metadata to $format...');
|
||||
updateItemStatus(
|
||||
item.id,
|
||||
@@ -2333,13 +2322,11 @@ result = await PlatformBridge.downloadWithExtensions(
|
||||
|
||||
_completedInSession++;
|
||||
|
||||
// Check if this track is already in download history
|
||||
final historyNotifier = ref.read(downloadHistoryProvider.notifier);
|
||||
final existingInHistory = historyNotifier.getBySpotifyId(trackToDownload.id) ??
|
||||
(trackToDownload.isrc != null ? historyNotifier.getByIsrc(trackToDownload.isrc!) : null);
|
||||
|
||||
if (wasExisting && existingInHistory != null) {
|
||||
// File exists and is already in download history - skip adding
|
||||
_log.i('Track already in library, skipping history update');
|
||||
await _notificationService.showDownloadComplete(
|
||||
trackName: item.track.name,
|
||||
|
||||
@@ -10,7 +10,6 @@ final _log = AppLogger('LocalLibrary');
|
||||
|
||||
const _lastScannedAtKey = 'local_library_last_scanned_at';
|
||||
|
||||
/// State for local library
|
||||
class LocalLibraryState {
|
||||
final List<LocalLibraryItem> items;
|
||||
final bool isScanning;
|
||||
@@ -92,7 +91,6 @@ class LocalLibraryState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Provider for local library state management
|
||||
class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
final LibraryDatabase _db = LibraryDatabase.instance;
|
||||
Timer? _progressTimer;
|
||||
@@ -120,7 +118,6 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
.map((e) => LocalLibraryItem.fromJson(e))
|
||||
.toList();
|
||||
|
||||
// Load lastScannedAt from SharedPreferences
|
||||
DateTime? lastScannedAt;
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
@@ -161,7 +158,6 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
scanErrorCount: 0,
|
||||
);
|
||||
|
||||
// Set cover cache directory before scanning
|
||||
try {
|
||||
final cacheDir = await getApplicationCacheDirectory();
|
||||
final coverCacheDir = '${cacheDir.path}/library_covers';
|
||||
@@ -171,23 +167,19 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
_log.w('Failed to set cover cache directory: $e');
|
||||
}
|
||||
|
||||
// Start progress polling
|
||||
_startProgressPolling();
|
||||
|
||||
try {
|
||||
final results = await PlatformBridge.scanLibraryFolder(folderPath);
|
||||
|
||||
// Convert results to LocalLibraryItem and save to database
|
||||
final items = <LocalLibraryItem>[];
|
||||
for (final json in results) {
|
||||
final item = LocalLibraryItem.fromJson(json);
|
||||
items.add(item);
|
||||
}
|
||||
|
||||
// Batch insert into database
|
||||
await _db.upsertBatch(items.map((e) => e.toJson()).toList());
|
||||
|
||||
// Save lastScannedAt to SharedPreferences
|
||||
final now = DateTime.now();
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
@@ -197,7 +189,6 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
_log.w('Failed to save lastScannedAt: $e');
|
||||
}
|
||||
|
||||
// Update state
|
||||
state = state.copyWith(
|
||||
items: items,
|
||||
isScanning: false,
|
||||
@@ -262,7 +253,6 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
Future<void> clearLibrary() async {
|
||||
await _db.clearAll();
|
||||
|
||||
// Clear lastScannedAt from SharedPreferences
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_lastScannedAtKey);
|
||||
|
||||
@@ -292,7 +292,6 @@ void setUseAllFilesAccess(bool enabled) {
|
||||
_saveSettings();
|
||||
}
|
||||
|
||||
// Local Library Settings
|
||||
void setLocalLibraryEnabled(bool enabled) {
|
||||
state = state.copyWith(localLibraryEnabled: enabled);
|
||||
_saveSettings();
|
||||
|
||||
@@ -695,7 +695,6 @@ child: ListTile(
|
||||
void _handleTap(BuildContext context, WidgetRef ref, {required bool isQueued, required bool isInHistory, required bool isInLocalLibrary}) async {
|
||||
if (isQueued) return;
|
||||
|
||||
// Check if track already exists in local library
|
||||
if (isInLocalLibrary) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(context.l10n.snackbarAlreadyInLibrary(track.name))));
|
||||
|
||||
@@ -103,7 +103,6 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
|
||||
bool _showTitleInAppBar = false;
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
// Selection mode state
|
||||
bool _isSelectionMode = false;
|
||||
final Set<String> _selectedAlbumIds = {};
|
||||
bool _isFetchingDiscography = false;
|
||||
@@ -112,7 +111,6 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Setup scroll listener for sticky title
|
||||
_scrollController.addListener(_onScroll);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
@@ -322,11 +320,9 @@ return PopScope(
|
||||
if (compilations.isNotEmpty)
|
||||
SliverToBoxAdapter(child: _buildAlbumSection(context.l10n.artistCompilations, compilations, colorScheme)),
|
||||
],
|
||||
// Add padding at bottom for selection bar
|
||||
SliverToBoxAdapter(child: SizedBox(height: _isSelectionMode ? 120 : 32)),
|
||||
],
|
||||
),
|
||||
// Selection action bar
|
||||
if (_isSelectionMode)
|
||||
_buildSelectionBar(context, colorScheme, albums),
|
||||
],
|
||||
@@ -404,14 +400,12 @@ return PopScope(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
// Close button
|
||||
IconButton(
|
||||
onPressed: _exitSelectionMode,
|
||||
icon: const Icon(Icons.close),
|
||||
tooltip: context.l10n.dialogCancel,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
// Selection info
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -433,13 +427,11 @@ return PopScope(
|
||||
],
|
||||
),
|
||||
),
|
||||
// Select all / Deselect button
|
||||
TextButton(
|
||||
onPressed: allSelected ? _deselectAll : () => _selectAll(allAlbums),
|
||||
child: Text(allSelected ? context.l10n.actionDeselect : context.l10n.actionSelectAll),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
// Download button
|
||||
FilledButton.icon(
|
||||
onPressed: selectedCount > 0 ? () => _downloadSelectedAlbums(context, selectedAlbums) : null,
|
||||
icon: const Icon(Icons.download, size: 18),
|
||||
@@ -473,7 +465,6 @@ return PopScope(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Handle bar
|
||||
Container(
|
||||
width: 40,
|
||||
height: 4,
|
||||
@@ -483,7 +474,6 @@ return PopScope(
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
// Title
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 0, 24, 16),
|
||||
child: Row(
|
||||
@@ -577,7 +567,6 @@ return PopScope(
|
||||
|
||||
setState(() => _isFetchingDiscography = true);
|
||||
|
||||
// Show progress dialog
|
||||
if (!mounted) {
|
||||
setState(() => _isFetchingDiscography = false);
|
||||
return;
|
||||
@@ -599,7 +588,6 @@ return PopScope(
|
||||
int fetchedCount = 0;
|
||||
int failedCount = 0;
|
||||
|
||||
// Fetch tracks from each album
|
||||
for (final album in albums) {
|
||||
if (!_isFetchingDiscography) break; // Cancelled
|
||||
|
||||
@@ -620,12 +608,10 @@ return PopScope(
|
||||
|
||||
setState(() => _isFetchingDiscography = false);
|
||||
|
||||
// Close progress dialog
|
||||
if (mounted) {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
}
|
||||
|
||||
// Show warning if some albums failed
|
||||
if (failedCount > 0 && mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(context.l10n.discographyFailedToFetch)),
|
||||
@@ -668,14 +654,12 @@ return PopScope(
|
||||
return;
|
||||
}
|
||||
|
||||
// Add to queue
|
||||
ref.read(downloadQueueProvider.notifier).addMultipleToQueue(
|
||||
tracksToQueue,
|
||||
service,
|
||||
qualityOverride: qualityOverride,
|
||||
);
|
||||
|
||||
// Show success message
|
||||
if (mounted) {
|
||||
final message = skippedCount > 0
|
||||
? context.l10n.discographySkippedDownloaded(tracksToQueue.length, skippedCount)
|
||||
@@ -698,14 +682,12 @@ return PopScope(
|
||||
|
||||
Future<List<Track>> _fetchAlbumTracks(ArtistAlbum album) async {
|
||||
if (album.providerId != null && album.providerId!.isNotEmpty) {
|
||||
// Extension album
|
||||
final result = await PlatformBridge.getAlbumWithExtension(album.providerId!, album.id);
|
||||
if (result != null && result['tracks'] != null) {
|
||||
final tracksList = result['tracks'] as List<dynamic>;
|
||||
return tracksList.map((t) => _parseTrack(t as Map<String, dynamic>)).toList();
|
||||
}
|
||||
} else if (album.id.startsWith('deezer:')) {
|
||||
// Deezer album
|
||||
final deezerId = album.id.replaceFirst('deezer:', '');
|
||||
final metadata = await PlatformBridge.getDeezerMetadata('album', deezerId);
|
||||
if (metadata['tracks'] != null) {
|
||||
@@ -713,7 +695,6 @@ return PopScope(
|
||||
return tracksList.map((t) => _parseTrackFromDeezer(t as Map<String, dynamic>, album)).toList();
|
||||
}
|
||||
} else {
|
||||
// Spotify album
|
||||
final url = 'https://open.spotify.com/album/${album.id}';
|
||||
final result = await PlatformBridge.handleURLWithExtension(url);
|
||||
if (result != null && result['tracks'] != null) {
|
||||
@@ -1068,7 +1049,6 @@ if (hasValidImage)
|
||||
void _handlePopularTrackTap(Track track, {required bool isQueued, required bool isInHistory, required bool isInLocalLibrary}) async {
|
||||
if (isQueued) return;
|
||||
|
||||
// Check if track already exists in local library
|
||||
if (isInLocalLibrary) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
||||
@@ -513,7 +513,6 @@ class _HomeTabState extends ConsumerState<HomeTab> with AutomaticKeepAliveClient
|
||||
Extension? currentSearchExtension;
|
||||
List<SearchFilter> searchFilters = [];
|
||||
|
||||
// Check if using extension search provider
|
||||
final isUsingExtensionSearch = currentSearchProvider != null &&
|
||||
currentSearchProvider.isNotEmpty &&
|
||||
extState.extensions.any((e) => e.id == currentSearchProvider && e.enabled);
|
||||
@@ -2567,7 +2566,6 @@ class _TrackItemWithStatus extends ConsumerWidget {
|
||||
void _handleTap(BuildContext context, WidgetRef ref, {required bool isQueued, required bool isInHistory, required bool isInLocalLibrary}) async {
|
||||
if (isQueued) return;
|
||||
|
||||
// Check if track already exists in local library
|
||||
if (isInLocalLibrary) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
||||
@@ -502,7 +502,6 @@ leading: track.coverUrl != null
|
||||
void _handleTap(BuildContext context, WidgetRef ref, {required bool isQueued, required bool isInHistory, required bool isInLocalLibrary}) async {
|
||||
if (isQueued) return;
|
||||
|
||||
// Check if track already exists in local library
|
||||
if (isInLocalLibrary) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(context.l10n.snackbarAlreadyInLibrary(track.name))));
|
||||
|
||||
@@ -28,13 +28,12 @@ class UnifiedLibraryItem {
|
||||
final String artistName;
|
||||
final String albumName;
|
||||
final String? coverUrl;
|
||||
final String? localCoverPath; // For local library items with extracted cover
|
||||
final String? localCoverPath;
|
||||
final String filePath;
|
||||
final String? quality;
|
||||
final DateTime addedAt;
|
||||
final LibraryItemSource source;
|
||||
|
||||
// Original items for navigation
|
||||
final DownloadHistoryItem? historyItem;
|
||||
final LocalLibraryItem? localItem;
|
||||
|
||||
@@ -258,7 +257,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
'Dec',
|
||||
];
|
||||
|
||||
// Search functionality
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
final FocusNode _searchFocusNode = FocusNode();
|
||||
String _searchQuery = '';
|
||||
|
||||
@@ -117,7 +117,6 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if folder exists
|
||||
if (!await Directory(libraryPath).exists()) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
||||
@@ -139,7 +139,6 @@ class HistoryDatabase {
|
||||
final prefs = await _prefs;
|
||||
final lastContainer = prefs.getString('ios_last_container_path');
|
||||
|
||||
// Skip if container hasn't changed
|
||||
if (lastContainer == _currentContainerPath) {
|
||||
_log.d('iOS container path unchanged, skipping migration');
|
||||
return false;
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'package:spotiflac_android/utils/logger.dart';
|
||||
|
||||
final _log = AppLogger('LibraryDatabase');
|
||||
|
||||
/// Represents a track in the user's local music library
|
||||
class LocalLibraryItem {
|
||||
final String id;
|
||||
final String trackName;
|
||||
@@ -14,7 +13,7 @@ class LocalLibraryItem {
|
||||
final String albumName;
|
||||
final String? albumArtist;
|
||||
final String filePath;
|
||||
final String? coverPath; // Path to extracted cover art
|
||||
final String? coverPath;
|
||||
final DateTime scannedAt;
|
||||
final String? isrc;
|
||||
final int? trackNumber;
|
||||
@@ -92,7 +91,6 @@ class LocalLibraryItem {
|
||||
String get albumKey => '${albumName.toLowerCase()}|${(albumArtist ?? artistName).toLowerCase()}';
|
||||
}
|
||||
|
||||
/// SQLite database service for local library
|
||||
class LibraryDatabase {
|
||||
static final LibraryDatabase instance = LibraryDatabase._init();
|
||||
static Database? _database;
|
||||
@@ -144,7 +142,6 @@ class LibraryDatabase {
|
||||
)
|
||||
''');
|
||||
|
||||
// Indexes for fast lookups
|
||||
await db.execute('CREATE INDEX idx_library_isrc ON library(isrc)');
|
||||
await db.execute('CREATE INDEX idx_library_track_artist ON library(track_name, artist_name)');
|
||||
await db.execute('CREATE INDEX idx_library_album ON library(album_name, album_artist)');
|
||||
@@ -163,7 +160,6 @@ class LibraryDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert JSON format (camelCase) to DB row (snake_case)
|
||||
Map<String, dynamic> _jsonToDbRow(Map<String, dynamic> json) {
|
||||
return {
|
||||
'id': json['id'],
|
||||
@@ -186,7 +182,6 @@ class LibraryDatabase {
|
||||
};
|
||||
}
|
||||
|
||||
/// Convert DB row (snake_case) to JSON format (camelCase)
|
||||
Map<String, dynamic> _dbRowToJson(Map<String, dynamic> row) {
|
||||
return {
|
||||
'id': row['id'],
|
||||
@@ -209,9 +204,8 @@ class LibraryDatabase {
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== CRUD Operations ====================
|
||||
// CRUD Operations
|
||||
|
||||
/// Insert or update a library item
|
||||
Future<void> upsert(Map<String, dynamic> json) async {
|
||||
final db = await database;
|
||||
await db.insert(
|
||||
@@ -221,7 +215,6 @@ class LibraryDatabase {
|
||||
);
|
||||
}
|
||||
|
||||
/// Batch insert multiple items
|
||||
Future<void> upsertBatch(List<Map<String, dynamic>> items) async {
|
||||
final db = await database;
|
||||
final batch = db.batch();
|
||||
@@ -238,7 +231,6 @@ class LibraryDatabase {
|
||||
_log.i('Batch inserted ${items.length} items');
|
||||
}
|
||||
|
||||
/// Get all library items ordered by album/artist
|
||||
Future<List<Map<String, dynamic>>> getAll({int? limit, int? offset}) async {
|
||||
final db = await database;
|
||||
final rows = await db.query(
|
||||
@@ -250,7 +242,6 @@ class LibraryDatabase {
|
||||
return rows.map(_dbRowToJson).toList();
|
||||
}
|
||||
|
||||
/// Get item by ID
|
||||
Future<Map<String, dynamic>?> getById(String id) async {
|
||||
final db = await database;
|
||||
final rows = await db.query(
|
||||
@@ -263,7 +254,6 @@ class LibraryDatabase {
|
||||
return _dbRowToJson(rows.first);
|
||||
}
|
||||
|
||||
/// Get item by ISRC - O(1) with index
|
||||
Future<Map<String, dynamic>?> getByIsrc(String isrc) async {
|
||||
final db = await database;
|
||||
final rows = await db.query(
|
||||
@@ -276,7 +266,6 @@ class LibraryDatabase {
|
||||
return _dbRowToJson(rows.first);
|
||||
}
|
||||
|
||||
/// Check if ISRC exists - O(1) with index
|
||||
Future<bool> existsByIsrc(String isrc) async {
|
||||
final db = await database;
|
||||
final result = await db.rawQuery(
|
||||
@@ -286,7 +275,6 @@ class LibraryDatabase {
|
||||
return result.isNotEmpty;
|
||||
}
|
||||
|
||||
/// Find by track name and artist (fuzzy match)
|
||||
Future<List<Map<String, dynamic>>> findByTrackAndArtist(
|
||||
String trackName,
|
||||
String artistName,
|
||||
@@ -300,7 +288,6 @@ class LibraryDatabase {
|
||||
return rows.map(_dbRowToJson).toList();
|
||||
}
|
||||
|
||||
/// Check if track exists by name and artist
|
||||
Future<Map<String, dynamic>?> findExisting({
|
||||
String? isrc,
|
||||
String? trackName,
|
||||
@@ -321,7 +308,6 @@ class LibraryDatabase {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Get all ISRCs as Set for fast in-memory lookup
|
||||
Future<Set<String>> getAllIsrcs() async {
|
||||
final db = await database;
|
||||
final rows = await db.rawQuery(
|
||||
@@ -330,7 +316,6 @@ class LibraryDatabase {
|
||||
return rows.map((r) => r['isrc'] as String).toSet();
|
||||
}
|
||||
|
||||
/// Get all track keys (name|artist) for matching
|
||||
Future<Set<String>> getAllTrackKeys() async {
|
||||
final db = await database;
|
||||
final rows = await db.rawQuery(
|
||||
@@ -339,19 +324,16 @@ class LibraryDatabase {
|
||||
return rows.map((r) => r['match_key'] as String).toSet();
|
||||
}
|
||||
|
||||
/// Delete by file path
|
||||
Future<void> deleteByPath(String filePath) async {
|
||||
final db = await database;
|
||||
await db.delete('library', where: 'file_path = ?', whereArgs: [filePath]);
|
||||
}
|
||||
|
||||
/// Delete by ID
|
||||
Future<void> delete(String id) async {
|
||||
final db = await database;
|
||||
await db.delete('library', where: 'id = ?', whereArgs: [id]);
|
||||
}
|
||||
|
||||
/// Delete items where file no longer exists
|
||||
Future<int> cleanupMissingFiles() async {
|
||||
final db = await database;
|
||||
final rows = await db.query('library', columns: ['id', 'file_path']);
|
||||
@@ -371,21 +353,18 @@ class LibraryDatabase {
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// Clear all library data
|
||||
Future<void> clearAll() async {
|
||||
final db = await database;
|
||||
await db.delete('library');
|
||||
_log.i('Cleared all library data');
|
||||
}
|
||||
|
||||
/// Get total count
|
||||
Future<int> getCount() async {
|
||||
final db = await database;
|
||||
final result = await db.rawQuery('SELECT COUNT(*) as count FROM library');
|
||||
return Sqflite.firstIntValue(result) ?? 0;
|
||||
}
|
||||
|
||||
/// Search library by query
|
||||
Future<List<Map<String, dynamic>>> search(String query, {int limit = 50}) async {
|
||||
final db = await database;
|
||||
final searchQuery = '%${query.toLowerCase()}%';
|
||||
@@ -399,7 +378,6 @@ class LibraryDatabase {
|
||||
return rows.map(_dbRowToJson).toList();
|
||||
}
|
||||
|
||||
/// Close database
|
||||
Future<void> close() async {
|
||||
final db = await database;
|
||||
await db.close();
|
||||
|
||||
Reference in New Issue
Block a user