Files
SpotiFLAC-Mobile/lib/services/library_duplicates.dart
T

81 lines
3.1 KiB
Dart

import 'package:sqflite/sqflite.dart';
import 'package:spotiflac_android/services/library_database_models.dart';
import 'package:spotiflac_android/services/library_schema.dart';
import 'package:spotiflac_android/utils/path_match_keys.dart';
/// Review physical copies, not index rows. History remains intact when a NAS
/// source is disabled, but must not suggest deleting its replacement SAF copy.
Future<List<IsrcDuplicateGroup>> readIsrcDuplicateGroups(
DatabaseExecutor db,
) async {
final rows = await db.rawQuery('''
WITH active_history AS (
SELECT h.* FROM history_db.history h
WHERE NOT (
h.file_path LIKE 'network://%'
AND EXISTS (
SELECT 1 FROM library_sources s
WHERE (s.enabled = 0 OR s.available = 0)
AND substr(h.file_path, 1, length(s.path)) = s.path
)
AND NOT EXISTS (
SELECT 1 FROM library_sources s
WHERE s.enabled = 1 AND s.available = 1
AND substr(h.file_path, 1, length(s.path)) = s.path
)
)
), merged AS (
SELECT h.id, 'downloaded' AS source, h.track_name, h.artist_name,
h.album_name, h.file_path, UPPER(TRIM(h.isrc)) AS isrc_key,
h.bit_depth, h.sample_rate, h.bitrate, h.format
FROM active_history h
WHERE h.isrc IS NOT NULL AND TRIM(h.isrc) != ''
UNION ALL
SELECT l.id, 'local' AS source, l.track_name, l.artist_name,
l.album_name, l.file_path, UPPER(TRIM(l.isrc)) AS isrc_key,
l.bit_depth, l.sample_rate, l.bitrate, l.format
FROM ${LibrarySchema.visibleView} l
WHERE l.isrc IS NOT NULL AND TRIM(l.isrc) != ''
)
SELECT * FROM merged
WHERE isrc_key IN (
SELECT isrc_key FROM merged GROUP BY isrc_key HAVING COUNT(*) > 1
)
ORDER BY isrc_key, bit_depth DESC, sample_rate DESC, bitrate DESC, source, id
''');
final groups = <String, List<IsrcDuplicateEntry>>{};
final pathsByIsrc = <String, Set<String>>{};
for (final row in rows) {
final isrc = row['isrc_key'] as String? ?? '';
final path = row['file_path'] as String? ?? '';
if (isrc.isEmpty || path.isEmpty) continue;
final keys = buildPhysicalPathMatchKeys(path);
final seen = pathsByIsrc.putIfAbsent(isrc, () => {});
final alreadySeen = keys.any(seen.contains);
seen.addAll(keys);
if (alreadySeen) continue;
groups
.putIfAbsent(isrc, () => [])
.add(
IsrcDuplicateEntry(
id: row['id'] as String,
source: row['source'] as String,
trackName: row['track_name'] as String? ?? '',
artistName: row['artist_name'] as String? ?? '',
albumName: row['album_name'] as String? ?? '',
filePath: path,
bitDepth: (row['bit_depth'] as num?)?.toInt(),
sampleRate: (row['sample_rate'] as num?)?.toInt(),
bitrate: (row['bitrate'] as num?)?.toInt(),
format: row['format'] as String?,
),
);
}
return [
for (final entry in groups.entries)
if (entry.value.length > 1)
IsrcDuplicateGroup(isrc: entry.key, entries: entry.value),
];
}