mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-05-24 00:34:07 +02:00
fix: preserve local convert format and library entries
This commit is contained in:
@@ -1631,7 +1631,12 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
|
||||
try {
|
||||
await PlatformBridge.safDelete(item.filePath);
|
||||
} catch (_) {}
|
||||
await localDb.deleteByPath(item.filePath);
|
||||
await localDb.replaceWithConvertedItem(
|
||||
item: item,
|
||||
newFilePath: safUri,
|
||||
targetFormat: targetFormat,
|
||||
bitrate: bitrate,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -1643,8 +1648,12 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
|
||||
} catch (_) {}
|
||||
}
|
||||
} else {
|
||||
// Regular file: just remove old entry, rescan will find the new one
|
||||
await localDb.deleteByPath(item.filePath);
|
||||
await localDb.replaceWithConvertedItem(
|
||||
item: item,
|
||||
newFilePath: newPath,
|
||||
targetFormat: targetFormat,
|
||||
bitrate: bitrate,
|
||||
);
|
||||
}
|
||||
|
||||
successCount++;
|
||||
|
||||
@@ -5853,13 +5853,27 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
final baseName = dotIdx > 0
|
||||
? oldFileName.substring(0, dotIdx)
|
||||
: oldFileName;
|
||||
final newExt = targetFormat.toLowerCase() == 'opus'
|
||||
? '.opus'
|
||||
: '.mp3';
|
||||
String newExt;
|
||||
String mimeType;
|
||||
switch (targetFormat.toLowerCase()) {
|
||||
case 'opus':
|
||||
newExt = '.opus';
|
||||
mimeType = 'audio/opus';
|
||||
break;
|
||||
case 'alac':
|
||||
newExt = '.m4a';
|
||||
mimeType = 'audio/mp4';
|
||||
break;
|
||||
case 'flac':
|
||||
newExt = '.flac';
|
||||
mimeType = 'audio/flac';
|
||||
break;
|
||||
default:
|
||||
newExt = '.mp3';
|
||||
mimeType = 'audio/mpeg';
|
||||
break;
|
||||
}
|
||||
final newFileName = '$baseName$newExt';
|
||||
final mimeType = targetFormat.toLowerCase() == 'opus'
|
||||
? 'audio/opus'
|
||||
: 'audio/mpeg';
|
||||
|
||||
final safUri = await PlatformBridge.createSafFileFromPath(
|
||||
treeUri: treeUri,
|
||||
@@ -5884,7 +5898,12 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
try {
|
||||
await PlatformBridge.safDelete(item.filePath);
|
||||
} catch (_) {}
|
||||
await LibraryDatabase.instance.deleteByPath(item.filePath);
|
||||
await LibraryDatabase.instance.replaceWithConvertedItem(
|
||||
item: item.localItem!,
|
||||
newFilePath: safUri,
|
||||
targetFormat: targetFormat,
|
||||
bitrate: bitrate,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -5903,7 +5922,12 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
clearAudioSpecs: true,
|
||||
);
|
||||
} else if (item.localItem != null) {
|
||||
await LibraryDatabase.instance.deleteByPath(item.filePath);
|
||||
await LibraryDatabase.instance.replaceWithConvertedItem(
|
||||
item: item.localItem!,
|
||||
newFilePath: newPath,
|
||||
targetFormat: targetFormat,
|
||||
bitrate: bitrate,
|
||||
);
|
||||
}
|
||||
|
||||
successCount++;
|
||||
|
||||
@@ -3929,8 +3929,50 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
|
||||
final newQuality = _buildConvertedQualityLabel(targetFormat, bitrate);
|
||||
|
||||
if (isSaf) {
|
||||
final treeUri = _downloadItem?.downloadTreeUri;
|
||||
final relativeDir = _downloadItem?.safRelativeDir ?? '';
|
||||
String? treeUri;
|
||||
String relativeDir = '';
|
||||
String oldFileName = '';
|
||||
if (_isLocalItem) {
|
||||
final uri = Uri.parse(cleanFilePath);
|
||||
final pathSegments = uri.pathSegments;
|
||||
final treeIdx = pathSegments.indexOf('tree');
|
||||
final docIdx = pathSegments.indexOf('document');
|
||||
if (treeIdx >= 0 && treeIdx + 1 < pathSegments.length) {
|
||||
final treeId = pathSegments[treeIdx + 1];
|
||||
treeUri =
|
||||
'content://${uri.authority}/tree/${Uri.encodeComponent(treeId)}';
|
||||
}
|
||||
if (docIdx >= 0 && docIdx + 1 < pathSegments.length) {
|
||||
final docPath = Uri.decodeFull(pathSegments[docIdx + 1]);
|
||||
final slashIdx = docPath.lastIndexOf('/');
|
||||
if (slashIdx >= 0) {
|
||||
oldFileName = docPath.substring(slashIdx + 1);
|
||||
final treeId = treeIdx >= 0 && treeIdx + 1 < pathSegments.length
|
||||
? Uri.decodeFull(pathSegments[treeIdx + 1])
|
||||
: '';
|
||||
if (treeId.isNotEmpty && docPath.startsWith(treeId)) {
|
||||
final afterTree = docPath.substring(treeId.length);
|
||||
final trimmed = afterTree.startsWith('/')
|
||||
? afterTree.substring(1)
|
||||
: afterTree;
|
||||
final lastSlash = trimmed.lastIndexOf('/');
|
||||
relativeDir = lastSlash >= 0
|
||||
? trimmed.substring(0, lastSlash)
|
||||
: '';
|
||||
}
|
||||
} else {
|
||||
oldFileName = docPath;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
treeUri = _downloadItem?.downloadTreeUri;
|
||||
relativeDir = _downloadItem?.safRelativeDir ?? '';
|
||||
oldFileName =
|
||||
(_downloadItem?.safFileName != null &&
|
||||
_downloadItem!.safFileName!.isNotEmpty)
|
||||
? _downloadItem!.safFileName!
|
||||
: _extractFileNameFromPathOrUri(cleanFilePath);
|
||||
}
|
||||
if (treeUri == null || treeUri.isEmpty) {
|
||||
try {
|
||||
await File(newPath).delete();
|
||||
@@ -3949,11 +3991,6 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
|
||||
return;
|
||||
}
|
||||
|
||||
final oldFileName =
|
||||
(_downloadItem?.safFileName != null &&
|
||||
_downloadItem!.safFileName!.isNotEmpty)
|
||||
? _downloadItem!.safFileName!
|
||||
: _extractFileNameFromPathOrUri(cleanFilePath);
|
||||
final dotIdx = oldFileName.lastIndexOf('.');
|
||||
final baseName = dotIdx > 0
|
||||
? oldFileName.substring(0, dotIdx)
|
||||
@@ -4022,6 +4059,14 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
|
||||
clearAudioSpecs: true,
|
||||
);
|
||||
await ref.read(downloadHistoryProvider.notifier).reloadFromStorage();
|
||||
} else {
|
||||
await LibraryDatabase.instance.replaceWithConvertedItem(
|
||||
item: _localLibraryItem!,
|
||||
newFilePath: safUri,
|
||||
targetFormat: targetFormat,
|
||||
bitrate: bitrate,
|
||||
);
|
||||
await ref.read(localLibraryProvider.notifier).reloadFromStorage();
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -4041,6 +4086,14 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
|
||||
clearAudioSpecs: true,
|
||||
);
|
||||
await ref.read(downloadHistoryProvider.notifier).reloadFromStorage();
|
||||
} else {
|
||||
await LibraryDatabase.instance.replaceWithConvertedItem(
|
||||
item: _localLibraryItem!,
|
||||
newFilePath: newPath,
|
||||
targetFormat: targetFormat,
|
||||
bitrate: bitrate,
|
||||
);
|
||||
await ref.read(localLibraryProvider.notifier).reloadFromStorage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -431,6 +431,45 @@ class LibraryDatabase {
|
||||
await db.delete('library', where: 'file_path = ?', whereArgs: [filePath]);
|
||||
}
|
||||
|
||||
Future<void> replaceWithConvertedItem({
|
||||
required LocalLibraryItem item,
|
||||
required String newFilePath,
|
||||
required String targetFormat,
|
||||
required String bitrate,
|
||||
}) async {
|
||||
final db = await database;
|
||||
final stat = await fileStat(newFilePath);
|
||||
final now = DateTime.now();
|
||||
final normalizedFormat = _normalizeConvertedFormat(targetFormat);
|
||||
final updated = item.toJson()
|
||||
..['id'] = _generateLibraryId(newFilePath)
|
||||
..['filePath'] = newFilePath
|
||||
..['scannedAt'] = now.toIso8601String()
|
||||
..['fileModTime'] = stat?.modified?.millisecondsSinceEpoch
|
||||
..['format'] = normalizedFormat
|
||||
..['bitrate'] = _convertedBitrate(
|
||||
targetFormat: targetFormat,
|
||||
bitrate: bitrate,
|
||||
);
|
||||
|
||||
if (normalizedFormat == 'mp3' || normalizedFormat == 'opus') {
|
||||
updated['bitDepth'] = null;
|
||||
}
|
||||
|
||||
await db.transaction((txn) async {
|
||||
await txn.delete(
|
||||
'library',
|
||||
where: 'id = ? OR file_path = ?',
|
||||
whereArgs: [item.id, item.filePath],
|
||||
);
|
||||
await txn.insert(
|
||||
'library',
|
||||
_jsonToDbRow(updated),
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> delete(String id) async {
|
||||
final db = await database;
|
||||
await db.delete('library', where: 'id = ?', whereArgs: [id]);
|
||||
@@ -602,4 +641,43 @@ class LibraryDatabase {
|
||||
}
|
||||
return totalDeleted;
|
||||
}
|
||||
|
||||
String _normalizeConvertedFormat(String targetFormat) {
|
||||
switch (targetFormat.trim().toLowerCase()) {
|
||||
case 'alac':
|
||||
return 'm4a';
|
||||
case 'flac':
|
||||
return 'flac';
|
||||
case 'opus':
|
||||
return 'opus';
|
||||
default:
|
||||
return 'mp3';
|
||||
}
|
||||
}
|
||||
|
||||
int? _convertedBitrate({
|
||||
required String targetFormat,
|
||||
required String bitrate,
|
||||
}) {
|
||||
switch (targetFormat.trim().toLowerCase()) {
|
||||
case 'mp3':
|
||||
case 'opus':
|
||||
final match = RegExp(r'(\d+)').firstMatch(bitrate);
|
||||
return match != null ? int.tryParse(match.group(1)!) : null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String _generateLibraryId(String filePath) {
|
||||
return 'lib_${_hashString(filePath).toRadixString(16)}';
|
||||
}
|
||||
|
||||
int _hashString(String input) {
|
||||
var hash = 5381;
|
||||
for (final codeUnit in input.codeUnits) {
|
||||
hash = (((hash << 5) + hash) + codeUnit) & 0xffffffff;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user