refactor(utils): share the FNV-1a 32-bit hash

Add fnv1a32 in hash_utils.dart for the scan output name (UTF-8 bytes) and
qualityVariantStagingLabel (UTF-16 code units). Unit tests pin outputs
computed from the original inline code.
This commit is contained in:
zarzet committed 2026-10-19 00:15:48 +07:00
1 parent 036589a4b1
commit adf2047df6
4 files changed
+47 -10

No files matched your search

+2 -5
View File
@@ -8,6 +8,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotiflac_android/services/download_request_payload.dart';
import 'package:spotiflac_android/utils/audio_format_utils.dart';
import 'package:spotiflac_android/utils/file_access.dart';
import 'package:spotiflac_android/utils/hash_utils.dart';
import 'package:spotiflac_android/utils/logger.dart';
part 'platform_bridge/bridge_codec.dart';
@@ -2326,11 +2327,7 @@ class PlatformBridge {
'method': method,
'arguments': arguments,
});
var hash = 0x811c9dc5;
for (final byte in utf8.encode(identity)) {
hash ^= byte;
hash = (hash * 0x01000193) & 0xffffffff;
}
final hash = fnv1a32(utf8.encode(identity));
final output = File(
'${scanDir.path}${Platform.pathSeparator}'
'library_scan_${hash.toRadixString(16).padLeft(8, '0')}.ndjson',
+2 -5
View File
@@ -1,3 +1,4 @@
import 'package:spotiflac_android/utils/hash_utils.dart';
import 'package:spotiflac_android/utils/int_utils.dart';
import 'package:spotiflac_android/utils/string_utils.dart';
@@ -290,11 +291,7 @@ int? _bitrateFromQuality(String? quality) {
}
String qualityVariantStagingLabel(String itemId) {
var hash = 0x811c9dc5;
for (final byte in itemId.codeUnits) {
hash ^= byte;
hash = (hash * 0x01000193) & 0xffffffff;
}
final hash = fnv1a32(itemId.codeUnits);
return 'qv_${hash.toRadixString(16).padLeft(8, '0')}';
}
+11
View File
@@ -0,0 +1,11 @@
/// FNV-1a 32-bit hash over [units] (UTF-8 bytes or UTF-16 code units).
///
/// The result feeds on-disk file names, so the algorithm must not change.
int fnv1a32(Iterable<int> units) {
var hash = 0x811c9dc5;
for (final unit in units) {
hash ^= unit;
hash = (hash * 0x01000193) & 0xffffffff;
}
return hash;
}
+32
View File
@@ -0,0 +1,32 @@
import 'dart:convert';
import 'package:flutter_test/flutter_test.dart';
import 'package:spotiflac_android/utils/audio_format_utils.dart';
import 'package:spotiflac_android/utils/hash_utils.dart';
String _hex(int hash) => hash.toRadixString(16).padLeft(8, '0');
void main() {
// Expected values were computed from the original inline implementations
// (scan output name over UTF-8 bytes, staging label over UTF-16 units).
test('fnv1a32 over UTF-8 bytes matches the scan output name hash', () {
expect(_hex(fnv1a32(utf8.encode(''))), '811c9dc5');
expect(_hex(fnv1a32(utf8.encode('track-123'))), '8277aead');
expect(
_hex(
fnv1a32(
utf8.encode('{"method":"scan","arguments":{"path":"content://x/y"}}'),
),
),
'94bc90df',
);
expect(_hex(fnv1a32(utf8.encode('café 日本'))), 'c843754b');
});
test('qualityVariantStagingLabel matches the original code units hash', () {
expect(qualityVariantStagingLabel(''), 'qv_811c9dc5');
expect(qualityVariantStagingLabel('a'), 'qv_e40c292c');
expect(qualityVariantStagingLabel('track-123'), 'qv_8277aead');
expect(qualityVariantStagingLabel('café 日本'), 'qv_73a64e9d');
});
}