mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-05-13 12:34:59 +02:00
refactor: remove Qobuz from built-in provider registry, add retired provider detection
Empty the builtInProviderRegistry now that all built-in providers are retired. Introduce isRetiredBuiltInDownloadProvider and isRetiredBuiltInMetadataProvider to classify deezer/qobuz/tidal/spotify as retired, replacing ad-hoc string checks. Add Dart-side metadata provider priority reconciliation that replaces retired providers with extensions declaring replacesBuiltInProviders. Remove getQobuzMetadata from native bridges and platform_bridge.dart. Update crowdin.yml with additional locale mappings.
This commit is contained in:
@@ -2889,14 +2889,6 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"getQobuzMetadata" -> {
|
||||
val resourceType = call.argument<String>("resource_type") ?: ""
|
||||
val resourceId = call.argument<String>("resource_id") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
Gobackend.getQobuzMetadata(resourceType, resourceId)
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"getProviderMetadata" -> {
|
||||
val providerId = call.argument<String>("provider_id") ?: ""
|
||||
val resourceType = call.argument<String>("resource_type") ?: ""
|
||||
|
||||
@@ -6,6 +6,7 @@ files:
|
||||
# Short codes for single-variant languages
|
||||
de: de
|
||||
es: es
|
||||
es-ES: es_ES
|
||||
fr: fr
|
||||
hi: hi
|
||||
id: id
|
||||
@@ -13,7 +14,10 @@ files:
|
||||
ko: ko
|
||||
nl: nl
|
||||
pt: pt
|
||||
pt-PT: pt_PT
|
||||
ru: ru
|
||||
tr: tr
|
||||
zh: zh
|
||||
# Full codes for Chinese variants
|
||||
zh-CN: zh_CN
|
||||
zh-TW: zh_TW
|
||||
|
||||
@@ -2358,7 +2358,6 @@ func ParseProviderURLJSON(url string) (string, error) {
|
||||
parse func(string) (string, string, error)
|
||||
}{
|
||||
{providerID: "deezer", parse: parseDeezerURL},
|
||||
{providerID: "qobuz", parse: parseQobuzURL},
|
||||
}
|
||||
|
||||
for _, parser := range parsers {
|
||||
|
||||
@@ -108,21 +108,7 @@ type builtInProviderSpec struct {
|
||||
Download func(req DownloadRequest) (DownloadResult, error) `json:"-"`
|
||||
}
|
||||
|
||||
var builtInProviderRegistry = []builtInProviderSpec{
|
||||
{
|
||||
ID: "qobuz",
|
||||
DisplayName: "Qobuz",
|
||||
SupportsMetadata: true,
|
||||
SupportsDownload: true,
|
||||
SupportsSearch: true,
|
||||
GetMetadata: GetQobuzMetadata,
|
||||
SearchAll: SearchQobuzAll,
|
||||
SearchTracks: func(query string, limit int) ([]ExtTrackMetadata, error) {
|
||||
return NewQobuzDownloader().SearchTracks(query, limit)
|
||||
},
|
||||
Download: downloadWithBuiltInQobuz,
|
||||
},
|
||||
}
|
||||
var builtInProviderRegistry = []builtInProviderSpec{}
|
||||
|
||||
func getBuiltInProviderSpecs() []builtInProviderSpec {
|
||||
specs := make([]builtInProviderSpec, len(builtInProviderRegistry))
|
||||
@@ -1224,7 +1210,7 @@ func sanitizeDownloadProviderPriority(providerIDs []string) []string {
|
||||
}
|
||||
|
||||
normalizedBuiltIn := strings.ToLower(providerID)
|
||||
if normalizedBuiltIn == "deezer" {
|
||||
if isRetiredBuiltInDownloadProvider(normalizedBuiltIn) {
|
||||
continue
|
||||
}
|
||||
if isBuiltInDownloadProvider(normalizedBuiltIn) {
|
||||
@@ -1242,6 +1228,38 @@ func sanitizeDownloadProviderPriority(providerIDs []string) []string {
|
||||
return sanitized
|
||||
}
|
||||
|
||||
func isRetiredBuiltInDownloadProvider(providerID string) bool {
|
||||
normalized := strings.ToLower(strings.TrimSpace(providerID))
|
||||
if normalized == "" {
|
||||
return false
|
||||
}
|
||||
if isBuiltInDownloadProvider(normalized) {
|
||||
return false
|
||||
}
|
||||
switch normalized {
|
||||
case "deezer", "qobuz", "tidal":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isRetiredBuiltInMetadataProvider(providerID string) bool {
|
||||
normalized := strings.ToLower(strings.TrimSpace(providerID))
|
||||
if normalized == "" {
|
||||
return false
|
||||
}
|
||||
if isBuiltInMetadataProvider(normalized) {
|
||||
return false
|
||||
}
|
||||
switch normalized {
|
||||
case "spotify", "qobuz", "tidal":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func SetExtensionFallbackProviderIDs(providerIDs []string) {
|
||||
extensionFallbackProviderIDsMu.Lock()
|
||||
defer extensionFallbackProviderIDsMu.Unlock()
|
||||
@@ -1309,7 +1327,7 @@ func SetMetadataProviderPriority(providerIDs []string) {
|
||||
seen := map[string]struct{}{}
|
||||
for _, providerID := range providerIDs {
|
||||
providerID = strings.TrimSpace(providerID)
|
||||
if providerID == "" || providerID == "spotify" {
|
||||
if providerID == "" || isRetiredBuiltInMetadataProvider(providerID) {
|
||||
continue
|
||||
}
|
||||
if _, exists := seen[providerID]; exists {
|
||||
|
||||
@@ -7,28 +7,22 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSetMetadataProviderPriorityPreservesExplicitProvidersOnly(t *testing.T) {
|
||||
func TestSetMetadataProviderPriorityStripsRetiredBuiltIns(t *testing.T) {
|
||||
original := GetMetadataProviderPriority()
|
||||
defer SetMetadataProviderPriority(original)
|
||||
|
||||
SetMetadataProviderPriority([]string{"qobuz"})
|
||||
got := GetMetadataProviderPriority()
|
||||
want := []string{"qobuz"}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("unexpected priority length: got %v want %v", got, want)
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Fatalf("unexpected priority at %d: got %v want %v", i, got, want)
|
||||
}
|
||||
if len(got) != 0 {
|
||||
t.Fatalf("expected retired built-in qobuz to be stripped, got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetExtensionFallbackProviderIDsSkipsBuiltInsAndDuplicates(t *testing.T) {
|
||||
func TestSetExtensionFallbackProviderIDsDedupesExtensions(t *testing.T) {
|
||||
original := GetExtensionFallbackProviderIDs()
|
||||
defer SetExtensionFallbackProviderIDs(original)
|
||||
|
||||
SetExtensionFallbackProviderIDs([]string{"ext-a", "qobuz", "ext-a", " ext-b "})
|
||||
SetExtensionFallbackProviderIDs([]string{"ext-a", "ext-a", " ext-b "})
|
||||
|
||||
got := GetExtensionFallbackProviderIDs()
|
||||
want := []string{"ext-a", "ext-b"}
|
||||
@@ -51,9 +45,6 @@ func TestIsExtensionFallbackAllowedDefaultsToAllExtensions(t *testing.T) {
|
||||
if !isExtensionFallbackAllowed("custom-ext") {
|
||||
t.Fatal("expected custom extension to be allowed when no fallback allowlist is configured")
|
||||
}
|
||||
if !isExtensionFallbackAllowed("qobuz") {
|
||||
t.Fatal("expected built-in provider to remain allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsExtensionFallbackAllowedRespectsAllowlist(t *testing.T) {
|
||||
@@ -80,7 +71,7 @@ func TestSetProviderPriorityRemovesRetiredDeezerDownloader(t *testing.T) {
|
||||
SetProviderPriority([]string{"deezer", "qobuz", "custom-ext"})
|
||||
|
||||
got := GetProviderPriority()
|
||||
want := []string{"qobuz", "custom-ext"}
|
||||
want := []string{"custom-ext"}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("unexpected priority length: got %v want %v", got, want)
|
||||
}
|
||||
@@ -247,7 +238,7 @@ func TestCanEmbedGenreLabelRequiresExistingAbsoluteLocalFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchTracksWithMetadataProvidersUsesPriorityAndDedupes(t *testing.T) {
|
||||
func TestSearchTracksWithMetadataProvidersIgnoresRetiredBuiltIns(t *testing.T) {
|
||||
originalPriority := GetMetadataProviderPriority()
|
||||
originalSearch := searchBuiltInMetadataTracksFunc
|
||||
defer func() {
|
||||
@@ -260,16 +251,7 @@ func TestSearchTracksWithMetadataProvidersUsesPriorityAndDedupes(t *testing.T) {
|
||||
var calls []string
|
||||
searchBuiltInMetadataTracksFunc = func(providerID, query string, limit int) ([]ExtTrackMetadata, error) {
|
||||
calls = append(calls, providerID)
|
||||
switch providerID {
|
||||
case "qobuz":
|
||||
return []ExtTrackMetadata{
|
||||
{ProviderID: "qobuz", SpotifyID: "qobuz:1", ISRC: "AAA111", Name: "First"},
|
||||
{ProviderID: "qobuz", SpotifyID: "qobuz:2", ISRC: "AAA111", Name: "Duplicate"},
|
||||
{ProviderID: "qobuz", SpotifyID: "qobuz:3", ISRC: "BBB222", Name: "Second"},
|
||||
}, nil
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
manager := getExtensionManager()
|
||||
@@ -277,13 +259,10 @@ func TestSearchTracksWithMetadataProvidersUsesPriorityAndDedupes(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("SearchTracksWithMetadataProviders returned error: %v", err)
|
||||
}
|
||||
if len(tracks) != 2 {
|
||||
t.Fatalf("unexpected track count: got %d want 2", len(tracks))
|
||||
if len(tracks) != 0 {
|
||||
t.Fatalf("expected no tracks from retired built-in provider, got %+v", tracks)
|
||||
}
|
||||
if tracks[0].ProviderID != "qobuz" || tracks[1].ProviderID != "qobuz" {
|
||||
t.Fatalf("unexpected track provider order: %+v", tracks)
|
||||
}
|
||||
if len(calls) != 1 || calls[0] != "qobuz" {
|
||||
t.Fatalf("unexpected provider call order: %v", calls)
|
||||
if len(calls) != 0 {
|
||||
t.Fatalf("expected retired built-in provider not to be queried, got %v", calls)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,14 +448,6 @@ import Gobackend // Import Go framework
|
||||
if let error = error { throw error }
|
||||
return response
|
||||
|
||||
case "getQobuzMetadata":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let resourceType = args["resource_type"] as! String
|
||||
let resourceId = args["resource_id"] as! String
|
||||
let response = GobackendGetQobuzMetadata(resourceType, resourceId, &error)
|
||||
if let error = error { throw error }
|
||||
return response
|
||||
|
||||
case "getProviderMetadata":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let providerId = args["provider_id"] as! String
|
||||
|
||||
@@ -758,6 +758,7 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
|
||||
state = state.copyWith(extensions: extensions);
|
||||
await _reconcileDownloadProviderPriority();
|
||||
await _reconcileDefaultDownloadService();
|
||||
await _reconcileMetadataProviderPriority();
|
||||
_reconcileSearchProvider();
|
||||
_log.d('Loaded ${extensions.length} extensions');
|
||||
|
||||
@@ -866,6 +867,7 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
|
||||
state = state.copyWith(extensions: extensions);
|
||||
await _reconcileDownloadProviderPriority();
|
||||
await _reconcileDefaultDownloadService();
|
||||
await _reconcileMetadataProviderPriority();
|
||||
_reconcileSearchProvider();
|
||||
|
||||
if (!enabled && ext != null) {
|
||||
@@ -914,6 +916,28 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
|
||||
_log.d('Reconciled provider priority after extension update: $sanitized');
|
||||
}
|
||||
|
||||
Future<void> _reconcileMetadataProviderPriority() async {
|
||||
if (state.metadataProviderPriority.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final replaced = _replaceRetiredBuiltInMetadataProviders(
|
||||
state.metadataProviderPriority,
|
||||
);
|
||||
final sanitized = _sanitizeMetadataProviderPriority(replaced);
|
||||
if (jsonEncode(sanitized) == jsonEncode(state.metadataProviderPriority)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_metadataProviderPriorityKey, jsonEncode(sanitized));
|
||||
await PlatformBridge.setMetadataProviderPriority(sanitized);
|
||||
state = state.copyWith(metadataProviderPriority: sanitized);
|
||||
_log.d(
|
||||
'Reconciled metadata provider priority after extension update: $sanitized',
|
||||
);
|
||||
}
|
||||
|
||||
String? _firstEnabledExtensionDownloadProviderId() {
|
||||
return state.extensions
|
||||
.where((ext) => ext.enabled && ext.hasDownloadProvider)
|
||||
@@ -951,6 +975,21 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
|
||||
.firstOrNull;
|
||||
}
|
||||
|
||||
String? replacedBuiltInMetadataProviderFor(String providerId) {
|
||||
final normalized = providerId.trim().toLowerCase();
|
||||
if (normalized.isEmpty) return null;
|
||||
|
||||
return state.extensions
|
||||
.where(
|
||||
(ext) =>
|
||||
ext.enabled &&
|
||||
ext.hasMetadataProvider &&
|
||||
ext.replacesBuiltInProviders.contains(normalized),
|
||||
)
|
||||
.map((ext) => ext.id)
|
||||
.firstOrNull;
|
||||
}
|
||||
|
||||
bool downloadProviderMatchesBuiltIn(
|
||||
String providerId,
|
||||
String builtInProviderId,
|
||||
@@ -1204,7 +1243,9 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
|
||||
if (savedJson != null) {
|
||||
final saved = jsonDecode(savedJson) as List<dynamic>;
|
||||
priority = _sanitizeMetadataProviderPriority(
|
||||
saved.map((e) => e as String).toList(),
|
||||
_replaceRetiredBuiltInMetadataProviders(
|
||||
saved.map((e) => e as String).toList(),
|
||||
),
|
||||
);
|
||||
_log.d('Loaded metadata provider priority from prefs: $priority');
|
||||
await prefs.setString(
|
||||
@@ -1233,7 +1274,9 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
|
||||
Future<void> setMetadataProviderPriority(List<String> priority) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final sanitized = _sanitizeMetadataProviderPriority(priority);
|
||||
final sanitized = _sanitizeMetadataProviderPriority(
|
||||
_replaceRetiredBuiltInMetadataProviders(priority),
|
||||
);
|
||||
await prefs.setString(
|
||||
_metadataProviderPriorityKey,
|
||||
jsonEncode(sanitized),
|
||||
@@ -1294,6 +1337,18 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
|
||||
];
|
||||
}
|
||||
|
||||
List<String> _replaceRetiredBuiltInMetadataProviders(List<String> input) {
|
||||
final result = <String>[];
|
||||
for (final provider in input) {
|
||||
final replacement = replacedBuiltInMetadataProviderFor(provider);
|
||||
final resolved = replacement ?? provider;
|
||||
if (!result.contains(resolved)) {
|
||||
result.add(resolved);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
List<String> _sanitizeMetadataProviderPriority(List<String> input) {
|
||||
final allowed = getAllMetadataProviders().toSet();
|
||||
final preferredOrder = getAllMetadataProviders();
|
||||
|
||||
@@ -524,22 +524,6 @@ class PlatformBridge {
|
||||
return jsonDecode(result as String) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> getQobuzMetadata(
|
||||
String resourceType,
|
||||
String resourceId,
|
||||
) async {
|
||||
final result = await _channel.invokeMethod('getQobuzMetadata', {
|
||||
'resource_type': resourceType,
|
||||
'resource_id': resourceId,
|
||||
});
|
||||
if (result == null) {
|
||||
throw Exception(
|
||||
'getQobuzMetadata returned null for $resourceType:$resourceId',
|
||||
);
|
||||
}
|
||||
return jsonDecode(result as String) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> parseProviderUrl(String url) async {
|
||||
final result = await _channel.invokeMethod('parseProviderUrl', {
|
||||
'url': url,
|
||||
|
||||
Reference in New Issue
Block a user