mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-23 01:50:44 +02:00
fix(conversion): preserve Unicode SAF document names
This commit is contained in:
@@ -940,36 +940,11 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
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;
|
||||
}
|
||||
final location = resolveSafDocumentLocation(cleanFilePath);
|
||||
if (location != null) {
|
||||
treeUri = location.treeUri;
|
||||
relativeDir = location.relativeDir;
|
||||
oldFileName = location.fileName;
|
||||
}
|
||||
} else {
|
||||
treeUri = _downloadItem?.downloadTreeUri;
|
||||
|
||||
@@ -20,6 +20,7 @@ import 'package:spotiflac_android/utils/lyrics_metadata_helper.dart';
|
||||
import 'package:spotiflac_android/utils/logger.dart';
|
||||
import 'package:spotiflac_android/widgets/batch_convert_sheet.dart';
|
||||
import 'package:spotiflac_android/widgets/batch_progress_dialog.dart';
|
||||
import 'package:spotiflac_android/utils/saf_display_path.dart';
|
||||
|
||||
final _batchActionsLog = AppLogger('BatchActions');
|
||||
|
||||
@@ -408,48 +409,13 @@ Future<void> _performBatchConversion(
|
||||
}
|
||||
} else if (isSaf && item.localItem != null) {
|
||||
failureStage = 'publish SAF output';
|
||||
final uri = Uri.parse(item.filePath);
|
||||
final pathSegments = uri.pathSegments;
|
||||
final location = resolveSafDocumentLocation(item.filePath);
|
||||
|
||||
String? treeUri;
|
||||
String relativeDir = '';
|
||||
String oldFileName = '';
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (treeUri != null && oldFileName.isNotEmpty) {
|
||||
if (location != null) {
|
||||
final published = await ConversionLibraryService.publishSafConversion(
|
||||
treeUri: treeUri,
|
||||
relativeDir: relativeDir,
|
||||
originalFileName: oldFileName,
|
||||
treeUri: location.treeUri,
|
||||
relativeDir: location.relativeDir,
|
||||
originalFileName: location.fileName,
|
||||
targetFormat: targetFormat,
|
||||
sourcePath: newPath,
|
||||
keepOriginal: keepOriginal,
|
||||
|
||||
@@ -1,3 +1,70 @@
|
||||
class SafDocumentLocation {
|
||||
final String treeUri;
|
||||
final String relativeDir;
|
||||
final String fileName;
|
||||
|
||||
const SafDocumentLocation({
|
||||
required this.treeUri,
|
||||
required this.relativeDir,
|
||||
required this.fileName,
|
||||
});
|
||||
}
|
||||
|
||||
/// Resolves the writable tree and relative destination of a SAF document URI.
|
||||
///
|
||||
/// [Uri.pathSegments] already percent-decodes every segment. Decoding those
|
||||
/// values again rejects ordinary Unicode filenames and corrupts literal `%`
|
||||
/// characters, so all values below are used directly.
|
||||
SafDocumentLocation? resolveSafDocumentLocation(String pathOrUri) {
|
||||
try {
|
||||
final uri = Uri.parse(pathOrUri.trim());
|
||||
if (uri.scheme != 'content' || uri.authority.isEmpty) return null;
|
||||
|
||||
final segments = uri.pathSegments;
|
||||
final treeIndex = segments.indexOf('tree');
|
||||
final documentIndex = segments.indexOf('document');
|
||||
if (treeIndex < 0 ||
|
||||
treeIndex + 1 >= segments.length ||
|
||||
documentIndex < 0 ||
|
||||
documentIndex + 1 >= segments.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final treeId = segments[treeIndex + 1];
|
||||
final documentId = segments[documentIndex + 1];
|
||||
if (treeId.isEmpty || documentId.isEmpty) return null;
|
||||
|
||||
final lastSlash = documentId.lastIndexOf('/');
|
||||
final fileName = lastSlash >= 0
|
||||
? documentId.substring(lastSlash + 1)
|
||||
: documentId;
|
||||
if (fileName.isEmpty) return null;
|
||||
|
||||
var relativeDir = '';
|
||||
final belongsToTree =
|
||||
documentId == treeId || documentId.startsWith('$treeId/');
|
||||
if (belongsToTree) {
|
||||
var relativePath = documentId.substring(treeId.length);
|
||||
if (relativePath.startsWith('/')) {
|
||||
relativePath = relativePath.substring(1);
|
||||
}
|
||||
final relativeSlash = relativePath.lastIndexOf('/');
|
||||
if (relativeSlash >= 0) {
|
||||
relativeDir = relativePath.substring(0, relativeSlash);
|
||||
}
|
||||
}
|
||||
|
||||
return SafDocumentLocation(
|
||||
treeUri:
|
||||
'${uri.scheme}://${uri.authority}/tree/${Uri.encodeComponent(treeId)}',
|
||||
relativeDir: relativeDir,
|
||||
fileName: fileName,
|
||||
);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String formatSafUriForDisplay(String pathOrUri) {
|
||||
if (pathOrUri.isEmpty || !pathOrUri.startsWith('content://')) {
|
||||
return pathOrUri;
|
||||
|
||||
Reference in New Issue
Block a user