From fbf479c10221a38ec115f6f21bc7b34a7398cf8f Mon Sep 17 00:00:00 2001 From: zarzet <42882290+zarzet@users.noreply.github.com> Date: Fri, 2 Oct 2026 00:31:00 +0700 Subject: [PATCH] fix(download): stop crashing native finalization on Android regex Android's regex engine rejects Pattern.UNICODE_CHARACTER_CLASS, so NativeFinalizationPolicy failed to initialize and every native download crashed while writing metadata. List Unicode spaces explicitly and cover the separator with a device test. --- .../NativeFinalizationPolicyDeviceTest.kt | 30 +++++++++++++++++++ .../spotiflac/NativeFinalizationPolicy.kt | 18 ++++++----- .../spotiflac/NativeFinalizationPolicyTest.kt | 2 ++ 3 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 android/app/src/androidTest/kotlin/com/zarz/spotiflac/NativeFinalizationPolicyDeviceTest.kt diff --git a/android/app/src/androidTest/kotlin/com/zarz/spotiflac/NativeFinalizationPolicyDeviceTest.kt b/android/app/src/androidTest/kotlin/com/zarz/spotiflac/NativeFinalizationPolicyDeviceTest.kt new file mode 100644 index 00000000..77f42834 --- /dev/null +++ b/android/app/src/androidTest/kotlin/com/zarz/spotiflac/NativeFinalizationPolicyDeviceTest.kt @@ -0,0 +1,30 @@ +package com.zarz.spotiflac + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Runs on Android's own regex engine: the JVM accepts regex flags that the + * device rejects when NativeFinalizationPolicy initializes during finalization. + */ +@RunWith(AndroidJUnit4::class) +class NativeFinalizationPolicyDeviceTest { + @Test + fun primaryArtistSeparatorCompilesAndSplitsOnDevice() { + for ((input, expected) in listOf( + "Calle 24, Chino Pacas" to "Calle 24", + "Artist A feat. Artist B" to "Artist A", + "Artist A\u00A0feat.\u00A0Artist B" to "Artist A", + "Artist A\u3000x\u3000Artist B" to "Artist A", + "Malcolm X" to "Malcolm X", + )) { + assertEquals(input, expected, NativeFinalizationPolicy.artistTagValue(input, "primary")) + } + assertEquals( + "Artist A, Artist B", + NativeFinalizationPolicy.artistTagValue("Artist A, Artist B", "joined"), + ) + } +} diff --git a/android/app/src/main/kotlin/com/zarz/spotiflac/NativeFinalizationPolicy.kt b/android/app/src/main/kotlin/com/zarz/spotiflac/NativeFinalizationPolicy.kt index 7fde4094..b5328179 100644 --- a/android/app/src/main/kotlin/com/zarz/spotiflac/NativeFinalizationPolicy.kt +++ b/android/app/src/main/kotlin/com/zarz/spotiflac/NativeFinalizationPolicy.kt @@ -1,7 +1,6 @@ package com.zarz.spotiflac import java.util.Locale -import java.util.regex.Pattern import kotlin.math.roundToInt /** @@ -374,12 +373,17 @@ internal object NativeFinalizationPolicy { ?: "" } - // Same separator set as the Rust writer and Dart's primaryArtistTagValue; - // Unicode classes keep \s aligned with their whitespace handling. - private val primaryArtistSeparator = Pattern.compile( - "\\s*[,;&]\\s*|\\s+x\\s+|\\s+(?:feat(?:uring)?|ft|with)\\.?(?:\\s+|$)", - Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CHARACTER_CLASS, - ).toRegex() + // Same separator set as the Rust writer and Dart's primaryArtistTagValue. + // Android's regex engine rejects Pattern.UNICODE_CHARACTER_CLASS when this + // object initializes, so Unicode spaces are listed explicitly instead. + private const val PRIMARY_ARTIST_SPACE = "[\\s\\p{Z}]" + private val primaryArtistSeparator = Regex( + "$PRIMARY_ARTIST_SPACE*[,;&]$PRIMARY_ARTIST_SPACE*" + + "|$PRIMARY_ARTIST_SPACE+x$PRIMARY_ARTIST_SPACE+" + + "|$PRIMARY_ARTIST_SPACE+(?:feat(?:uring)?|ft|with)\\.?" + + "(?:$PRIMARY_ARTIST_SPACE+|$)", + RegexOption.IGNORE_CASE, + ) /** * Artist tag value for [mode]. Only "primary" changes the value: the first diff --git a/android/app/src/test/kotlin/com/zarz/spotiflac/NativeFinalizationPolicyTest.kt b/android/app/src/test/kotlin/com/zarz/spotiflac/NativeFinalizationPolicyTest.kt index 62d3ee15..9140ab30 100644 --- a/android/app/src/test/kotlin/com/zarz/spotiflac/NativeFinalizationPolicyTest.kt +++ b/android/app/src/test/kotlin/com/zarz/spotiflac/NativeFinalizationPolicyTest.kt @@ -21,6 +21,8 @@ class NativeFinalizationPolicyTest { "Artist A with Artist B" to "Artist A", "Artist A x Artist B" to "Artist A", "Artist A X Artist B" to "Artist A", + "Artist A\u00A0feat.\u00A0Artist B" to "Artist A", + "Artist A\u3000x\u3000Artist B" to "Artist A", " , Artist A, Artist B" to "Artist A", "Malcolm X" to "Malcolm X", "Artist Without Fear" to "Artist Without Fear",