mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-02 09:08:35 +02:00
refactor(bridge): remove dead cross-layer platform bridge methods
This commit is contained in:
@@ -1826,66 +1826,6 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
private fun runPostProcessingSaf(fileUriStr: String, metadataJson: String): String {
|
||||
val uri = Uri.parse(fileUriStr)
|
||||
val doc = DocumentFile.fromSingleUri(this, uri)
|
||||
?: return errorJson("SAF file not found")
|
||||
|
||||
val tempInput = copyUriToTemp(uri) ?: return errorJson("Failed to copy SAF file to temp")
|
||||
val tempDir = File(tempInput).parentFile?.absolutePath ?: ""
|
||||
if (tempDir.isNotBlank()) {
|
||||
try {
|
||||
Gobackend.allowDownloadDir(tempDir)
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
|
||||
val response = Gobackend.runPostProcessingJSON(tempInput, metadataJson)
|
||||
val respObj = JSONObject(response)
|
||||
if (!respObj.optBoolean("success", false)) {
|
||||
try {
|
||||
File(tempInput).delete()
|
||||
} catch (_: Exception) {}
|
||||
return response
|
||||
}
|
||||
|
||||
val newPath = respObj.optString("new_file_path", "")
|
||||
val outputPath = if (newPath.isNotBlank()) newPath else tempInput
|
||||
val outputFile = File(outputPath)
|
||||
if (!outputFile.exists()) {
|
||||
try {
|
||||
File(tempInput).delete()
|
||||
} catch (_: Exception) {}
|
||||
respObj.put("success", false)
|
||||
respObj.put("error", "postProcess output not found")
|
||||
return respObj.toString()
|
||||
}
|
||||
|
||||
val newName = outputFile.name
|
||||
if (!newName.isNullOrBlank() && doc.name != null && doc.name != newName) {
|
||||
try {
|
||||
doc.renameTo(newName)
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
|
||||
val writeOk = writeUriFromPath(uri, outputFile.absolutePath)
|
||||
if (!writeOk) {
|
||||
respObj.put("success", false)
|
||||
respObj.put("error", "failed to write postProcess output to SAF")
|
||||
return respObj.toString()
|
||||
}
|
||||
|
||||
try {
|
||||
if (outputPath != tempInput) {
|
||||
outputFile.delete()
|
||||
}
|
||||
File(tempInput).delete()
|
||||
} catch (_: Exception) {}
|
||||
|
||||
respObj.put("new_file_path", uri.toString())
|
||||
respObj.put("file_path", uri.toString())
|
||||
return respObj.toString()
|
||||
}
|
||||
|
||||
private fun runPostProcessingSafV2(fileUriStr: String, metadataJson: String): String {
|
||||
val uri = Uri.parse(fileUriStr)
|
||||
val doc = DocumentFile.fromSingleUri(this, uri)
|
||||
@@ -2255,32 +2195,12 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"getDownloadProgress" -> {
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
Gobackend.getDownloadProgress()
|
||||
}
|
||||
result.success(parseJsonPayload(response))
|
||||
}
|
||||
"getAllDownloadProgress" -> {
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
Gobackend.getAllDownloadProgress()
|
||||
}
|
||||
result.success(parseJsonPayload(response))
|
||||
}
|
||||
"initItemProgress" -> {
|
||||
val itemId = call.argument<String>("item_id") ?: ""
|
||||
withContext(Dispatchers.IO) {
|
||||
Gobackend.initItemProgress(itemId)
|
||||
}
|
||||
result.success(null)
|
||||
}
|
||||
"finishItemProgress" -> {
|
||||
val itemId = call.argument<String>("item_id") ?: ""
|
||||
withContext(Dispatchers.IO) {
|
||||
Gobackend.finishItemProgress(itemId)
|
||||
}
|
||||
result.success(null)
|
||||
}
|
||||
"clearItemProgress" -> {
|
||||
val itemId = call.argument<String>("item_id") ?: ""
|
||||
withContext(Dispatchers.IO) {
|
||||
@@ -2324,14 +2244,6 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
}
|
||||
result.success(null)
|
||||
}
|
||||
"checkDuplicate" -> {
|
||||
val outputDir = call.argument<String>("output_dir") ?: ""
|
||||
val isrc = call.argument<String>("isrc") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
Gobackend.checkDuplicate(outputDir, isrc)
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"checkDuplicatesBatch" -> {
|
||||
val outputDir = call.argument<String>("output_dir") ?: ""
|
||||
val tracksJson = call.argument<String>("tracks") ?: "[]"
|
||||
@@ -2588,16 +2500,6 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
result.error("share_failed", e.message, null)
|
||||
}
|
||||
}
|
||||
"fetchLyrics" -> {
|
||||
val spotifyId = call.argument<String>("spotify_id") ?: ""
|
||||
val trackName = call.argument<String>("track_name") ?: ""
|
||||
val artistName = call.argument<String>("artist_name") ?: ""
|
||||
val durationMs = call.argument<Int>("duration_ms")?.toLong() ?: 0L
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
Gobackend.fetchLyrics(spotifyId, trackName, artistName, durationMs)
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"getLyricsLRC" -> {
|
||||
val spotifyId = call.argument<String>("spotify_id") ?: ""
|
||||
val trackName = call.argument<String>("track_name") ?: ""
|
||||
@@ -3116,14 +3018,6 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
}
|
||||
result.success(null)
|
||||
}
|
||||
"getDeezerRelatedArtists" -> {
|
||||
val artistId = call.argument<String>("artist_id") ?: ""
|
||||
val limit = call.argument<Int>("limit") ?: 12
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
Gobackend.getDeezerRelatedArtists(artistId, limit.toLong())
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"getProviderMetadata" -> {
|
||||
val providerId = call.argument<String>("provider_id") ?: ""
|
||||
val resourceType = call.argument<String>("resource_type") ?: ""
|
||||
@@ -3354,14 +3248,6 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"searchTracksWithExtensions" -> {
|
||||
val query = call.argument<String>("query") ?: ""
|
||||
val limit = call.argument<Int>("limit") ?: 20
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
Gobackend.searchTracksWithExtensionsJSON(query, limit.toLong())
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"searchTracksWithMetadataProviders" -> {
|
||||
val query = call.argument<String>("query") ?: ""
|
||||
val limit = call.argument<Int>("limit") ?: 20
|
||||
@@ -3499,12 +3385,6 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
}
|
||||
result.success(null)
|
||||
}
|
||||
"getSearchProviders" -> {
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
Gobackend.getSearchProvidersJSON()
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"handleURLWithExtension" -> {
|
||||
val url = call.argument<String>("url") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
@@ -3519,21 +3399,11 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"getURLHandlers" -> {
|
||||
"getTrackPlatformLinks" -> {
|
||||
val spotifyId = call.argument<String>("spotify_id") ?: ""
|
||||
val isrc = call.argument<String>("isrc") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
Gobackend.getURLHandlersJSON()
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"runPostProcessing" -> {
|
||||
val filePath = call.argument<String>("file_path") ?: ""
|
||||
val metadataJson = call.argument<String>("metadata") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
if (filePath.startsWith("content://")) {
|
||||
runPostProcessingSaf(filePath, metadataJson)
|
||||
} else {
|
||||
Gobackend.runPostProcessingJSON(filePath, metadataJson)
|
||||
}
|
||||
Gobackend.getTrackPlatformLinksJSON(spotifyId, isrc)
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
@@ -3562,12 +3432,6 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"getPostProcessingProviders" -> {
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
Gobackend.getPostProcessingProvidersJSON()
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"initExtensionRepo" -> {
|
||||
val cacheDir = call.argument<String>("cache_dir") ?: ""
|
||||
withContext(Dispatchers.IO) {
|
||||
@@ -3637,13 +3501,6 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"getExtensionBrowseCategories" -> {
|
||||
val extensionId = call.argument<String>("extension_id") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
Gobackend.getExtensionBrowseCategoriesJSON(extensionId)
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"setLibraryCoverCacheDir" -> {
|
||||
val cacheDir = call.argument<String>("cache_dir") ?: ""
|
||||
withContext(Dispatchers.IO) {
|
||||
|
||||
@@ -19,7 +19,6 @@ const (
|
||||
deezerTrackURL = deezerBaseURL + "/track/%s"
|
||||
deezerAlbumURL = deezerBaseURL + "/album/%s"
|
||||
deezerArtistURL = deezerBaseURL + "/artist/%s"
|
||||
deezerArtistRelatedURL = deezerBaseURL + "/artist/%s/related"
|
||||
deezerPlaylistURL = deezerBaseURL + "/playlist/%s"
|
||||
|
||||
deezerCacheTTL = 10 * time.Minute
|
||||
@@ -846,66 +845,6 @@ func (c *DeezerClient) fetchAlbumTrackCounts(ctx context.Context, albums []Artis
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (c *DeezerClient) GetRelatedArtists(ctx context.Context, artistID string, limit int) ([]SearchArtistResult, error) {
|
||||
normalizedArtistID := strings.TrimSpace(strings.TrimPrefix(artistID, "deezer:"))
|
||||
if normalizedArtistID == "" {
|
||||
return nil, fmt.Errorf("invalid Deezer artist ID")
|
||||
}
|
||||
|
||||
effectiveLimit := limit
|
||||
if effectiveLimit <= 0 {
|
||||
effectiveLimit = 12
|
||||
}
|
||||
|
||||
relatedURL := fmt.Sprintf("%s?limit=%d", fmt.Sprintf(deezerArtistRelatedURL, normalizedArtistID), effectiveLimit)
|
||||
var relatedResp struct {
|
||||
Data []struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Picture string `json:"picture"`
|
||||
PictureMedium string `json:"picture_medium"`
|
||||
PictureBig string `json:"picture_big"`
|
||||
PictureXL string `json:"picture_xl"`
|
||||
NbFan int `json:"nb_fan"`
|
||||
} `json:"data"`
|
||||
Error *struct {
|
||||
Type string `json:"type"`
|
||||
Message string `json:"message"`
|
||||
Code int `json:"code"`
|
||||
} `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
if err := c.getJSON(ctx, relatedURL, &relatedResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if relatedResp.Error != nil {
|
||||
return nil, fmt.Errorf("deezer related artists error: %s", relatedResp.Error.Message)
|
||||
}
|
||||
|
||||
result := make([]SearchArtistResult, 0, len(relatedResp.Data))
|
||||
for _, artist := range relatedResp.Data {
|
||||
imageURL := artist.PictureXL
|
||||
if imageURL == "" {
|
||||
imageURL = artist.PictureBig
|
||||
}
|
||||
if imageURL == "" {
|
||||
imageURL = artist.PictureMedium
|
||||
}
|
||||
if imageURL == "" {
|
||||
imageURL = artist.Picture
|
||||
}
|
||||
|
||||
result = append(result, SearchArtistResult{
|
||||
ID: fmt.Sprintf("deezer:%d", artist.ID),
|
||||
Name: artist.Name,
|
||||
Images: imageURL,
|
||||
Followers: artist.NbFan,
|
||||
Popularity: 0,
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *DeezerClient) GetPlaylist(ctx context.Context, playlistID string) (*PlaylistResponsePayload, error) {
|
||||
playlistURL := fmt.Sprintf(deezerPlaylistURL, playlistID)
|
||||
|
||||
|
||||
@@ -78,17 +78,6 @@ func TestDeezerClientWithFakeHTTP(t *testing.T) {
|
||||
t.Fatalf("cached artist = %#v/%v", cachedArtist, err)
|
||||
}
|
||||
|
||||
related, err := client.GetRelatedArtists(ctx, "deezer:301", 3)
|
||||
if err != nil {
|
||||
t.Fatalf("GetRelatedArtists: %v", err)
|
||||
}
|
||||
if len(related) != 1 || related[0].ID != "deezer:302" {
|
||||
t.Fatalf("related = %#v", related)
|
||||
}
|
||||
if _, err := client.GetRelatedArtists(ctx, "", 0); err == nil {
|
||||
t.Fatal("expected invalid related artist ID")
|
||||
}
|
||||
|
||||
playlist, err := client.GetPlaylist(ctx, "401")
|
||||
if err != nil {
|
||||
t.Fatalf("GetPlaylist: %v", err)
|
||||
|
||||
+10
-11
@@ -63,6 +63,16 @@ func SetSongLinkNetworkOptions(allowHTTP, insecureTLS bool) {
|
||||
SetNetworkCompatibilityOptions(allowHTTP, insecureTLS)
|
||||
}
|
||||
|
||||
// GetTrackPlatformLinksJSON returns {"platforms": {platformID: url}} for a
|
||||
// track, resolved via song.link (memory-cached; either ID may be empty).
|
||||
func GetTrackPlatformLinksJSON(spotifyTrackID string, isrc string) (string, error) {
|
||||
links, err := NewSongLinkClient().GetTrackPlatformLinks(spotifyTrackID, isrc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return marshalJSONString(map[string]any{"platforms": links})
|
||||
}
|
||||
|
||||
func SetDownloadDirectory(path string) error {
|
||||
return setDownloadDir(path)
|
||||
}
|
||||
@@ -74,17 +84,6 @@ func AllowDownloadDir(path string) {
|
||||
AddAllowedDownloadDir(path)
|
||||
}
|
||||
|
||||
func CheckDuplicate(outputDir, isrc string) (string, error) {
|
||||
existingFile, exists := CheckISRCExists(outputDir, isrc)
|
||||
|
||||
result := map[string]any{
|
||||
"exists": exists,
|
||||
"filepath": existingFile,
|
||||
}
|
||||
|
||||
return marshalJSONString(result)
|
||||
}
|
||||
|
||||
func CheckDuplicatesBatch(outputDir, tracksJSON string) (string, error) {
|
||||
return CheckFilesExistParallel(outputDir, tracksJSON)
|
||||
}
|
||||
|
||||
@@ -17,22 +17,6 @@ func GetTrackCacheSize() int {
|
||||
func ClearTrackIDCache() {
|
||||
}
|
||||
|
||||
func GetDeezerRelatedArtists(artistID string, limit int) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
client := GetDeezerClient()
|
||||
artists, err := client.GetRelatedArtists(ctx, artistID, limit)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resp := map[string]any{
|
||||
"artists": artists,
|
||||
}
|
||||
return marshalJSONString(resp)
|
||||
}
|
||||
|
||||
func GetDeezerMetadata(resourceType, resourceID string) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -383,12 +383,6 @@ func DownloadByStrategy(requestJSON string) (string, error) {
|
||||
return errorResponse("Extension providers are disabled; built-in download providers have been retired")
|
||||
}
|
||||
|
||||
func GetDownloadProgress() string {
|
||||
progress := getProgress()
|
||||
jsonBytes, _ := json.Marshal(progress)
|
||||
return string(jsonBytes)
|
||||
}
|
||||
|
||||
func GetAllDownloadProgress() string {
|
||||
return GetMultiProgress()
|
||||
}
|
||||
@@ -401,10 +395,6 @@ func InitItemProgress(itemID string) {
|
||||
StartItemProgress(itemID)
|
||||
}
|
||||
|
||||
func FinishItemProgress(itemID string) {
|
||||
CompleteItemProgress(itemID)
|
||||
}
|
||||
|
||||
func ClearItemProgress(itemID string) {
|
||||
RemoveItemProgress(itemID)
|
||||
}
|
||||
|
||||
@@ -432,16 +432,6 @@ func SetExtensionSettingsJSON(extensionID, settingsJSON string) error {
|
||||
return manager.InitializeExtension(extensionID, settings)
|
||||
}
|
||||
|
||||
func SearchTracksWithExtensionsJSON(query string, limit int) (string, error) {
|
||||
manager := getExtensionManager()
|
||||
tracks, err := manager.SearchTracksWithExtensions(query, limit)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return marshalJSONString(tracks)
|
||||
}
|
||||
|
||||
func SearchTracksWithMetadataProvidersJSON(query string, limit int, includeExtensions bool) (string, error) {
|
||||
manager := getExtensionManager()
|
||||
tracks, err := manager.SearchTracksWithMetadataProviders(query, limit, includeExtensions)
|
||||
@@ -821,24 +811,6 @@ func CustomSearchWithExtensionJSONWithRequestID(extensionID, query string, optio
|
||||
return marshalJSONString(result)
|
||||
}
|
||||
|
||||
func GetSearchProvidersJSON() (string, error) {
|
||||
manager := getExtensionManager()
|
||||
providers := manager.GetSearchProviders()
|
||||
|
||||
result := make([]map[string]any, 0, len(providers))
|
||||
for _, p := range providers {
|
||||
result = append(result, map[string]any{
|
||||
"id": p.extension.ID,
|
||||
"display_name": p.extension.Manifest.DisplayName,
|
||||
"placeholder": p.extension.Manifest.SearchBehavior.Placeholder,
|
||||
"primary": p.extension.Manifest.SearchBehavior.Primary,
|
||||
"icon": p.extension.Manifest.SearchBehavior.Icon,
|
||||
})
|
||||
}
|
||||
|
||||
return marshalJSONString(result)
|
||||
}
|
||||
|
||||
func HandleURLWithExtensionJSON(url string) (string, error) {
|
||||
manager := getExtensionManager()
|
||||
resultWithID, err := manager.HandleURLWithExtension(url)
|
||||
@@ -1025,39 +997,6 @@ func FindURLHandlerJSON(url string) string {
|
||||
return handler.extension.ID
|
||||
}
|
||||
|
||||
func GetURLHandlersJSON() (string, error) {
|
||||
manager := getExtensionManager()
|
||||
handlers := manager.GetURLHandlers()
|
||||
|
||||
result := make([]map[string]any, 0, len(handlers))
|
||||
for _, h := range handlers {
|
||||
result = append(result, map[string]any{
|
||||
"id": h.extension.ID,
|
||||
"display_name": h.extension.Manifest.DisplayName,
|
||||
"patterns": h.extension.Manifest.URLHandler.Patterns,
|
||||
})
|
||||
}
|
||||
|
||||
return marshalJSONString(result)
|
||||
}
|
||||
|
||||
func RunPostProcessingJSON(filePath, metadataJSON string) (string, error) {
|
||||
var metadata map[string]any
|
||||
if metadataJSON != "" {
|
||||
if err := json.Unmarshal([]byte(metadataJSON), &metadata); err != nil {
|
||||
metadata = make(map[string]any)
|
||||
}
|
||||
}
|
||||
|
||||
manager := getExtensionManager()
|
||||
result, err := manager.RunPostProcessing(filePath, metadata)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return marshalJSONString(result)
|
||||
}
|
||||
|
||||
func RunPostProcessingV2JSON(inputJSON, metadataJSON string) (string, error) {
|
||||
var metadata map[string]any
|
||||
if metadataJSON != "" {
|
||||
@@ -1082,33 +1021,6 @@ func RunPostProcessingV2JSON(inputJSON, metadataJSON string) (string, error) {
|
||||
return marshalJSONString(result)
|
||||
}
|
||||
|
||||
func GetPostProcessingProvidersJSON() (string, error) {
|
||||
manager := getExtensionManager()
|
||||
providers := manager.GetPostProcessingProviders()
|
||||
|
||||
result := make([]map[string]any, 0, len(providers))
|
||||
for _, p := range providers {
|
||||
hooks := make([]map[string]any, 0)
|
||||
for _, h := range p.extension.Manifest.GetPostProcessingHooks() {
|
||||
hooks = append(hooks, map[string]any{
|
||||
"id": h.ID,
|
||||
"name": h.Name,
|
||||
"description": h.Description,
|
||||
"default_enabled": h.DefaultEnabled,
|
||||
"supported_formats": h.SupportedFormats,
|
||||
})
|
||||
}
|
||||
|
||||
result = append(result, map[string]any{
|
||||
"id": p.extension.ID,
|
||||
"display_name": p.extension.Manifest.DisplayName,
|
||||
"hooks": hooks,
|
||||
})
|
||||
}
|
||||
|
||||
return marshalJSONString(result)
|
||||
}
|
||||
|
||||
func callExtensionFunctionJSON(extensionID, functionName string, timeout time.Duration) (string, error) {
|
||||
return callExtensionFunctionJSONWithRequestID(extensionID, functionName, timeout, "")
|
||||
}
|
||||
@@ -1187,10 +1099,6 @@ func GetExtensionHomeFeedJSONWithRequestID(extensionID, requestID string) (strin
|
||||
return callExtensionFunctionJSONWithRequestID(extensionID, "getHomeFeed", 60*time.Second, requestID)
|
||||
}
|
||||
|
||||
func GetExtensionBrowseCategoriesJSON(extensionID string) (string, error) {
|
||||
return callExtensionFunctionJSON(extensionID, "getBrowseCategories", 30*time.Second)
|
||||
}
|
||||
|
||||
func CancelExtensionRequestJSON(requestID string) {
|
||||
cancelExtensionRequest(requestID)
|
||||
}
|
||||
|
||||
@@ -7,25 +7,6 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func FetchLyrics(spotifyID, trackName, artistName string, durationMs int64) (string, error) {
|
||||
client := NewLyricsClient()
|
||||
durationSec := float64(durationMs) / 1000.0
|
||||
lyrics, err := client.FetchLyricsAllSources(spotifyID, trackName, artistName, durationSec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
result := map[string]any{
|
||||
"success": true,
|
||||
"source": lyrics.Source,
|
||||
"sync_type": lyrics.SyncType,
|
||||
"lines": lyrics.Lines,
|
||||
"instrumental": lyrics.Instrumental,
|
||||
}
|
||||
|
||||
return marshalJSONString(result)
|
||||
}
|
||||
|
||||
func GetLyricsLRC(spotifyID, trackName, artistName string, filePath string, durationMs int64) (string, error) {
|
||||
if filePath != "" {
|
||||
lyrics, err := ExtractLyrics(filePath)
|
||||
|
||||
@@ -20,9 +20,6 @@ func TestLyricsExportWrappersWithoutNetwork(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if jsonText, err := FetchLyrics("spotify-1", "Song Instrumental", "Artist", 180000); err != nil || !strings.Contains(jsonText, `"instrumental":true`) {
|
||||
t.Fatalf("FetchLyrics instrumental = %q/%v", jsonText, err)
|
||||
}
|
||||
if lrc, err := GetLyricsLRC("spotify-1", "Song Instrumental", "Artist", "", 180000); err != nil || lrc != "[instrumental:true]" {
|
||||
t.Fatalf("GetLyricsLRC instrumental = %q/%v", lrc, err)
|
||||
}
|
||||
@@ -112,6 +109,19 @@ func TestSongLinkExportWrappersWithFakeClient(t *testing.T) {
|
||||
t.Fatal("songLinkExtractDeezerTrackID mismatch")
|
||||
}
|
||||
|
||||
if linksJSON, err := GetTrackPlatformLinksJSON("spotify-1", ""); err != nil ||
|
||||
!strings.Contains(linksJSON, `"tidal":"https://listen.tidal.com/track/202"`) ||
|
||||
!strings.Contains(linksJSON, `"spotify":`) {
|
||||
t.Fatalf("GetTrackPlatformLinksJSON = %q/%v", linksJSON, err)
|
||||
}
|
||||
// Second call must come from the links cache, not a new request.
|
||||
if cached, hit, cachedErr := trackPlatformLinksCacheLookup(GetSongLinkRegion() + "|spotify:spotify-1"); !hit || cachedErr || cached["tidal"] == "" {
|
||||
t.Fatalf("trackPlatformLinksCacheLookup = %#v hit=%v err=%v", cached, hit, cachedErr)
|
||||
}
|
||||
if _, err := GetTrackPlatformLinksJSON("", ""); err == nil {
|
||||
t.Fatal("GetTrackPlatformLinksJSON with empty IDs should error")
|
||||
}
|
||||
|
||||
deezerClient = &DeezerClient{
|
||||
httpClient: &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
body := fakeDeezerResponse(req.URL.Path, req.URL.RawQuery)
|
||||
|
||||
@@ -134,10 +134,9 @@ func TestExportsJSONWrappersAndExtensionManagerSurface(t *testing.T) {
|
||||
}
|
||||
|
||||
InitItemProgress("item-1")
|
||||
FinishItemProgress("item-1")
|
||||
ClearItemProgress("item-1")
|
||||
CancelDownload("item-1")
|
||||
if GetDownloadProgress() == "" || GetAllDownloadProgress() == "" || GetAllDownloadProgressDelta(0) == "" {
|
||||
if GetAllDownloadProgress() == "" || GetAllDownloadProgressDelta(0) == "" {
|
||||
t.Fatal("expected progress JSON")
|
||||
}
|
||||
CleanupConnections()
|
||||
@@ -194,9 +193,6 @@ func TestExportsJSONWrappersAndExtensionManagerSurface(t *testing.T) {
|
||||
if err := SetDownloadDirectory(dir); err != nil {
|
||||
t.Fatalf("SetDownloadDirectory: %v", err)
|
||||
}
|
||||
if duplicateJSON, err := CheckDuplicate(dir, ""); err != nil || !strings.Contains(duplicateJSON, "exists") {
|
||||
t.Fatalf("CheckDuplicate = %q/%v", duplicateJSON, err)
|
||||
}
|
||||
if batchJSON, err := CheckDuplicatesBatch(dir, `[{"isrc":"","track_name":"Song","artist_name":"Artist"}]`); err != nil || !strings.Contains(batchJSON, "Song") {
|
||||
t.Fatalf("CheckDuplicatesBatch = %q/%v", batchJSON, err)
|
||||
}
|
||||
@@ -262,9 +258,6 @@ func TestExportsJSONWrappersAndExtensionManagerSurface(t *testing.T) {
|
||||
t.Fatal("expected settings JSON error")
|
||||
}
|
||||
|
||||
if jsonText, err := SearchTracksWithExtensionsJSON("song", 5); err != nil || !strings.Contains(jsonText, "search-1") {
|
||||
t.Fatalf("SearchTracksWithExtensionsJSON = %q/%v", jsonText, err)
|
||||
}
|
||||
if jsonText, err := SearchTracksWithMetadataProvidersJSON("song", 5, true); err != nil || !strings.Contains(jsonText, "search-1") {
|
||||
t.Fatalf("SearchTracksWithMetadataProvidersJSON = %q/%v", jsonText, err)
|
||||
}
|
||||
@@ -362,9 +355,6 @@ func TestExportsJSONWrappersAndExtensionManagerSurface(t *testing.T) {
|
||||
if _, err := GetDeezerMetadata("bad", "1"); err == nil {
|
||||
t.Fatal("expected unsupported Deezer metadata type")
|
||||
}
|
||||
if jsonText, err := GetDeezerRelatedArtists("301", 2); err != nil || !strings.Contains(jsonText, "Related") {
|
||||
t.Fatalf("GetDeezerRelatedArtists = %q/%v", jsonText, err)
|
||||
}
|
||||
if jsonText, err := GetDeezerExtendedMetadata("101"); err != nil || !strings.Contains(jsonText, "Label") {
|
||||
t.Fatalf("GetDeezerExtendedMetadata = %q/%v", jsonText, err)
|
||||
}
|
||||
@@ -385,37 +375,22 @@ func TestExportsJSONWrappersAndExtensionManagerSurface(t *testing.T) {
|
||||
if customJSON, err := CustomSearchWithExtensionJSONWithRequestID(ext.ID, "needle", `not-json`, "req-custom"); err != nil || !strings.Contains(customJSON, "custom-1") {
|
||||
t.Fatalf("CustomSearchWithExtensionJSONWithRequestID = %q/%v", customJSON, err)
|
||||
}
|
||||
if providersJSON, err := GetSearchProvidersJSON(); err != nil || !strings.Contains(providersJSON, "coverage-ext") {
|
||||
t.Fatalf("GetSearchProvidersJSON = %q/%v", providersJSON, err)
|
||||
}
|
||||
if found := FindURLHandlerJSON("https://example.test/track/1"); found != ext.ID {
|
||||
t.Fatalf("FindURLHandlerJSON = %q", found)
|
||||
}
|
||||
if handlersJSON, err := GetURLHandlersJSON(); err != nil || !strings.Contains(handlersJSON, "coverage-ext") {
|
||||
t.Fatalf("GetURLHandlersJSON = %q/%v", handlersJSON, err)
|
||||
}
|
||||
if handledJSON, err := HandleURLWithExtensionJSON("https://example.test/track/1"); err != nil || !strings.Contains(handledJSON, "url-track") {
|
||||
t.Fatalf("HandleURLWithExtensionJSON = %q/%v", handledJSON, err)
|
||||
}
|
||||
if postJSON, err := RunPostProcessingJSON(filepath.Join(dir, "song.flac"), `{"title":"Song"}`); err != nil || !strings.Contains(postJSON, "success") {
|
||||
t.Fatalf("RunPostProcessingJSON = %q/%v", postJSON, err)
|
||||
}
|
||||
v2Input := `{"path":"` + escapeJSONPath(filepath.Join(dir, "song.flac")) + `","uri":"content://song","name":"song.flac","mime_type":"audio/flac","size":10}`
|
||||
if postJSON, err := RunPostProcessingV2JSON(v2Input, `not-json`); err != nil || !strings.Contains(postJSON, "success") {
|
||||
t.Fatalf("RunPostProcessingV2JSON = %q/%v", postJSON, err)
|
||||
}
|
||||
if postProviders, err := GetPostProcessingProvidersJSON(); err != nil || !strings.Contains(postProviders, "hook") {
|
||||
t.Fatalf("GetPostProcessingProvidersJSON = %q/%v", postProviders, err)
|
||||
}
|
||||
if feedJSON, err := GetExtensionHomeFeedJSON(ext.ID); err != nil || !strings.Contains(feedJSON, "home-1") {
|
||||
t.Fatalf("GetExtensionHomeFeedJSON = %q/%v", feedJSON, err)
|
||||
}
|
||||
if feedJSON, err := GetExtensionHomeFeedJSONWithRequestID(ext.ID, "req-home"); err != nil || !strings.Contains(feedJSON, "home-1") {
|
||||
t.Fatalf("GetExtensionHomeFeedJSONWithRequestID = %q/%v", feedJSON, err)
|
||||
}
|
||||
if categoriesJSON, err := GetExtensionBrowseCategoriesJSON(ext.ID); err != nil || !strings.Contains(categoriesJSON, "cat-1") {
|
||||
t.Fatalf("GetExtensionBrowseCategoriesJSON = %q/%v", categoriesJSON, err)
|
||||
}
|
||||
CancelExtensionRequestJSON("req-home")
|
||||
|
||||
storeDir := filepath.Join(dir, "store")
|
||||
|
||||
@@ -92,9 +92,9 @@ func TestExtensionProviderWrapperFullSurface(t *testing.T) {
|
||||
t.Fatalf("url result = %#v", urlResult)
|
||||
}
|
||||
|
||||
post, err := provider.PostProcess(filepath.Join(t.TempDir(), "song.flac"), map[string]any{"title": "Song"}, "hook")
|
||||
post, err := provider.PostProcessV2(PostProcessInput{Path: filepath.Join(t.TempDir(), "song.flac")}, map[string]any{"title": "Song"}, "hook")
|
||||
if err != nil {
|
||||
t.Fatalf("PostProcess: %v", err)
|
||||
t.Fatalf("PostProcessV2: %v", err)
|
||||
}
|
||||
if !post.Success || post.BitDepth != 24 || post.SampleRate != 96000 {
|
||||
t.Fatalf("post = %#v", post)
|
||||
|
||||
@@ -847,11 +847,9 @@ func postProcessInputMap(input PostProcessInput) map[string]any {
|
||||
|
||||
const PostProcessTimeout = 2 * time.Minute
|
||||
|
||||
// postProcessCommon backs both PostProcess (V1) and PostProcessV2. V1 probes
|
||||
// only extension.postProcess (its original contract: V2-only extensions are
|
||||
// not invoked via V1); V2 probes postProcessV2 first, then falls back to
|
||||
// postProcess.
|
||||
func (p *extensionProviderWrapper) postProcessCommon(input PostProcessInput, metadata map[string]any, hookID string, preferV2 bool) (*PostProcessResult, error) {
|
||||
// PostProcessV2 probes extension.postProcessV2 first, then falls back to the
|
||||
// legacy extension.postProcess contract.
|
||||
func (p *extensionProviderWrapper) PostProcessV2(input PostProcessInput, metadata map[string]any, hookID string) (*PostProcessResult, error) {
|
||||
if !p.extension.Manifest.HasPostProcessing() {
|
||||
return nil, fmt.Errorf("extension '%s' does not support post-processing", p.extension.ID)
|
||||
}
|
||||
@@ -861,19 +859,13 @@ func (p *extensionProviderWrapper) postProcessCommon(input PostProcessInput, met
|
||||
|
||||
filePath := input.Path
|
||||
|
||||
perfName := "postProcess"
|
||||
var invoke func(*goja.Runtime) (goja.Value, error)
|
||||
if preferV2 {
|
||||
perfName = "postProcessV2"
|
||||
inputMap := postProcessInputMap(input)
|
||||
invoke = func(vm *goja.Runtime) (goja.Value, error) {
|
||||
if hasExtensionMethod(vm, "postProcessV2") {
|
||||
return invokeExtensionMethod(vm, "postProcessV2", inputMap, metadata, hookID)
|
||||
}
|
||||
return invokeExtensionMethod(vm, "postProcess", filePath, metadata, hookID)
|
||||
perfName := "postProcessV2"
|
||||
inputMap := postProcessInputMap(input)
|
||||
invoke := func(vm *goja.Runtime) (goja.Value, error) {
|
||||
if hasExtensionMethod(vm, "postProcessV2") {
|
||||
return invokeExtensionMethod(vm, "postProcessV2", inputMap, metadata, hookID)
|
||||
}
|
||||
} else {
|
||||
invoke = extensionMethodInvocation("postProcess", filePath, metadata, hookID)
|
||||
return invokeExtensionMethod(vm, "postProcess", filePath, metadata, hookID)
|
||||
}
|
||||
|
||||
result, err := callExtension(p, extCallOpts{
|
||||
@@ -898,14 +890,6 @@ func (p *extensionProviderWrapper) postProcessCommon(input PostProcessInput, met
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *extensionProviderWrapper) PostProcess(filePath string, metadata map[string]any, hookID string) (*PostProcessResult, error) {
|
||||
return p.postProcessCommon(PostProcessInput{Path: filePath}, metadata, hookID, false)
|
||||
}
|
||||
|
||||
func (p *extensionProviderWrapper) PostProcessV2(input PostProcessInput, metadata map[string]any, hookID string) (*PostProcessResult, error) {
|
||||
return p.postProcessCommon(input, metadata, hookID, true)
|
||||
}
|
||||
|
||||
type ExtLyricsResult struct {
|
||||
Lines []ExtLyricsLine `json:"lines"`
|
||||
SyncType string `json:"syncType"`
|
||||
|
||||
@@ -34,49 +34,6 @@ func (m *extensionManager) GetDownloadProviders() []*extensionProviderWrapper {
|
||||
return providers
|
||||
}
|
||||
|
||||
func (m *extensionManager) SearchTracksWithExtensions(query string, limit int) ([]ExtTrackMetadata, error) {
|
||||
providers := m.GetMetadataProviders()
|
||||
if len(providers) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
providerByID := make(map[string]*extensionProviderWrapper, len(providers))
|
||||
orderedProviders := make([]*extensionProviderWrapper, 0, len(providers))
|
||||
for _, provider := range providers {
|
||||
providerByID[provider.extension.ID] = provider
|
||||
}
|
||||
for _, providerID := range GetMetadataProviderPriority() {
|
||||
if provider := providerByID[providerID]; provider != nil {
|
||||
orderedProviders = append(orderedProviders, provider)
|
||||
delete(providerByID, providerID)
|
||||
}
|
||||
}
|
||||
if len(providerByID) > 0 {
|
||||
remainingIDs := make([]string, 0, len(providerByID))
|
||||
for providerID := range providerByID {
|
||||
remainingIDs = append(remainingIDs, providerID)
|
||||
}
|
||||
sort.Strings(remainingIDs)
|
||||
for _, providerID := range remainingIDs {
|
||||
orderedProviders = append(orderedProviders, providerByID[providerID])
|
||||
}
|
||||
}
|
||||
|
||||
var allTracks []ExtTrackMetadata
|
||||
for _, provider := range orderedProviders {
|
||||
result, err := provider.SearchTracks(query, limit)
|
||||
if err != nil {
|
||||
GoLog("[Extension] Search error from %s: %v\n", provider.extension.ID, err)
|
||||
continue
|
||||
}
|
||||
if result != nil {
|
||||
allTracks = append(allTracks, result.Tracks...)
|
||||
}
|
||||
}
|
||||
|
||||
return allTracks, nil
|
||||
}
|
||||
|
||||
func metadataTrackDedupKey(track ExtTrackMetadata) string {
|
||||
if isrc := strings.TrimSpace(track.ISRC); isrc != "" {
|
||||
return "isrc:" + strings.ToUpper(isrc)
|
||||
@@ -176,32 +133,6 @@ func (m *extensionManager) SearchTracksWithMetadataProvidersForItemID(query stri
|
||||
return tracks, nil
|
||||
}
|
||||
|
||||
func (m *extensionManager) GetSearchProviders() []*extensionProviderWrapper {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
var providers []*extensionProviderWrapper
|
||||
for _, ext := range m.extensions {
|
||||
if ext.Enabled && ext.Manifest.HasCustomSearch() && ext.Error == "" {
|
||||
providers = append(providers, newExtensionProviderWrapper(ext))
|
||||
}
|
||||
}
|
||||
return providers
|
||||
}
|
||||
|
||||
func (m *extensionManager) GetURLHandlers() []*extensionProviderWrapper {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
var providers []*extensionProviderWrapper
|
||||
for _, ext := range m.extensions {
|
||||
if ext.Enabled && ext.Manifest.HasURLHandler() && ext.Error == "" {
|
||||
providers = append(providers, newExtensionProviderWrapper(ext))
|
||||
}
|
||||
}
|
||||
return providers
|
||||
}
|
||||
|
||||
func (m *extensionManager) FindURLHandler(url string) *extensionProviderWrapper {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
@@ -252,21 +183,13 @@ func (m *extensionManager) GetPostProcessingProviders() []*extensionProviderWrap
|
||||
return providers
|
||||
}
|
||||
|
||||
// runPostProcessingCommon backs both RunPostProcessing (V1) and
|
||||
// RunPostProcessingV2. V1 delegates into this shared loop with an equivalent
|
||||
// PostProcessInput; preferV2 controls whether each hook is invoked via
|
||||
// provider.PostProcessV2 or provider.PostProcess, so V1 keeps calling
|
||||
// PostProcess (not PostProcessV2) exactly as it did before.
|
||||
func (m *extensionManager) runPostProcessingCommon(input PostProcessInput, metadata map[string]any, preferV2 bool) (*PostProcessResult, error) {
|
||||
func (m *extensionManager) RunPostProcessingV2(input PostProcessInput, metadata map[string]any) (*PostProcessResult, error) {
|
||||
providers := m.GetPostProcessingProviders()
|
||||
if len(providers) == 0 {
|
||||
return &PostProcessResult{Success: true, NewFilePath: input.Path, NewFileURI: input.URI}, nil
|
||||
}
|
||||
|
||||
logTag := "[PostProcess]"
|
||||
if preferV2 {
|
||||
logTag = "[PostProcessV2]"
|
||||
}
|
||||
logTag := "[PostProcessV2]"
|
||||
|
||||
currentInput := input
|
||||
for _, provider := range providers {
|
||||
@@ -295,13 +218,7 @@ func (m *extensionManager) runPostProcessingCommon(input PostProcessInput, metad
|
||||
|
||||
GoLog("%s Running hook %s from %s on %s\n", logTag, hook.ID, provider.extension.ID, currentInput.Path)
|
||||
|
||||
var result *PostProcessResult
|
||||
var err error
|
||||
if preferV2 {
|
||||
result, err = provider.PostProcessV2(currentInput, metadata, hook.ID)
|
||||
} else {
|
||||
result, err = provider.PostProcess(currentInput.Path, metadata, hook.ID)
|
||||
}
|
||||
result, err := provider.PostProcessV2(currentInput, metadata, hook.ID)
|
||||
if err != nil {
|
||||
GoLog("%s Hook %s failed: %v\n", logTag, hook.ID, err)
|
||||
continue
|
||||
@@ -360,18 +277,6 @@ func validatePostProcessResult(ext *loadedExtension, input PostProcessInput, res
|
||||
return fmt.Errorf("replacement file path is outside allowed directories")
|
||||
}
|
||||
|
||||
func (m *extensionManager) RunPostProcessing(filePath string, metadata map[string]any) (*PostProcessResult, error) {
|
||||
result, err := m.runPostProcessingCommon(PostProcessInput{Path: filePath}, metadata, false)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
return &PostProcessResult{Success: result.Success, NewFilePath: result.NewFilePath}, nil
|
||||
}
|
||||
|
||||
func (m *extensionManager) RunPostProcessingV2(input PostProcessInput, metadata map[string]any) (*PostProcessResult, error) {
|
||||
return m.runPostProcessingCommon(input, metadata, true)
|
||||
}
|
||||
|
||||
func (m *extensionManager) GetLyricsProviders() []*extensionProviderWrapper {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
@@ -108,25 +108,6 @@ func markMultiProgressDirtyIfChangedLocked(item *ItemProgress, before progressBr
|
||||
}
|
||||
}
|
||||
|
||||
func getProgress() DownloadProgress {
|
||||
multiMu.RLock()
|
||||
defer multiMu.RUnlock()
|
||||
|
||||
for _, item := range multiProgress.Items {
|
||||
return DownloadProgress{
|
||||
CurrentFile: item.ItemID,
|
||||
Progress: item.Progress * 100,
|
||||
BytesTotal: item.BytesTotal,
|
||||
BytesReceived: item.BytesReceived,
|
||||
IsDownloading: item.IsDownloading,
|
||||
Status: item.Status,
|
||||
Stage: item.Stage,
|
||||
}
|
||||
}
|
||||
|
||||
return DownloadProgress{}
|
||||
}
|
||||
|
||||
func GetMultiProgress() string {
|
||||
multiMu.RLock()
|
||||
if !multiProgressDirty {
|
||||
|
||||
@@ -440,26 +440,10 @@ import Gobackend
|
||||
if let error = error { throw error }
|
||||
return response
|
||||
|
||||
case "getDownloadProgress":
|
||||
let response = GobackendGetDownloadProgress()
|
||||
return parseJsonPayload(response as String? ?? "{}")
|
||||
|
||||
case "getAllDownloadProgress":
|
||||
let response = GobackendGetAllDownloadProgress()
|
||||
return parseJsonPayload(response as String? ?? "{}")
|
||||
|
||||
case "initItemProgress":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let itemId = args["item_id"] as! String
|
||||
GobackendInitItemProgress(itemId)
|
||||
return nil
|
||||
|
||||
case "finishItemProgress":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let itemId = args["item_id"] as! String
|
||||
GobackendFinishItemProgress(itemId)
|
||||
return nil
|
||||
|
||||
case "clearItemProgress":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let itemId = args["item_id"] as! String
|
||||
@@ -498,14 +482,6 @@ import Gobackend
|
||||
GobackendSetAllowPrivateNetwork(allowed)
|
||||
return nil
|
||||
|
||||
case "checkDuplicate":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let outputDir = args["output_dir"] as! String
|
||||
let isrc = args["isrc"] as! String
|
||||
let response = GobackendCheckDuplicate(outputDir, isrc, &error)
|
||||
if let error = error { throw error }
|
||||
return response
|
||||
|
||||
case "checkDuplicatesBatch":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let outputDir = args["output_dir"] as! String
|
||||
@@ -541,16 +517,6 @@ import Gobackend
|
||||
let response = GobackendSanitizeFilename(filename)
|
||||
return response
|
||||
|
||||
case "fetchLyrics":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let spotifyId = args["spotify_id"] as! String
|
||||
let trackName = args["track_name"] as! String
|
||||
let artistName = args["artist_name"] as! String
|
||||
let durationMs = args["duration_ms"] as? Int64 ?? 0
|
||||
let response = GobackendFetchLyrics(spotifyId, trackName, artistName, durationMs, &error)
|
||||
if let error = error { throw error }
|
||||
return response
|
||||
|
||||
case "getLyricsLRC":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let spotifyId = args["spotify_id"] as! String
|
||||
@@ -645,14 +611,6 @@ import Gobackend
|
||||
if let error = error { throw error }
|
||||
return response
|
||||
|
||||
case "getDeezerRelatedArtists":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let artistId = args["artist_id"] as! String
|
||||
let limit = args["limit"] as? Int ?? 12
|
||||
let response = GobackendGetDeezerRelatedArtists(artistId, Int(limit), &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
|
||||
@@ -845,14 +803,6 @@ import Gobackend
|
||||
if let error = error { throw error }
|
||||
return response
|
||||
|
||||
case "searchTracksWithExtensions":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let query = args["query"] as! String
|
||||
let limit = args["limit"] as? Int ?? 20
|
||||
let response = GobackendSearchTracksWithExtensionsJSON(query, Int(limit), &error)
|
||||
if let error = error { throw error }
|
||||
return response
|
||||
|
||||
case "searchTracksWithMetadataProviders":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let query = args["query"] as! String
|
||||
@@ -997,11 +947,6 @@ import Gobackend
|
||||
GobackendCancelExtensionRequestJSON(requestId)
|
||||
return nil
|
||||
|
||||
case "getSearchProviders":
|
||||
let response = GobackendGetSearchProvidersJSON(&error)
|
||||
if let error = error { throw error }
|
||||
return response
|
||||
|
||||
case "handleURLWithExtension":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let url = args["url"] as! String
|
||||
@@ -1015,19 +960,14 @@ import Gobackend
|
||||
let response = GobackendFindURLHandlerJSON(url)
|
||||
return response
|
||||
|
||||
case "getURLHandlers":
|
||||
let response = GobackendGetURLHandlersJSON(&error)
|
||||
case "getTrackPlatformLinks":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let spotifyId = args["spotify_id"] as? String ?? ""
|
||||
let isrc = args["isrc"] as? String ?? ""
|
||||
let response = GobackendGetTrackPlatformLinksJSON(spotifyId, isrc, &error)
|
||||
if let error = error { throw error }
|
||||
return response
|
||||
|
||||
case "runPostProcessing":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let filePath = args["file_path"] as! String
|
||||
let metadataJson = args["metadata"] as? String ?? ""
|
||||
let response = GobackendRunPostProcessingJSON(filePath, metadataJson, &error)
|
||||
if let error = error { throw error }
|
||||
return response
|
||||
|
||||
case "runPostProcessingV2":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let inputJson = args["input"] as? String ?? ""
|
||||
@@ -1036,11 +976,6 @@ import Gobackend
|
||||
if let error = error { throw error }
|
||||
return response
|
||||
|
||||
case "getPostProcessingProviders":
|
||||
let response = GobackendGetPostProcessingProvidersJSON(&error)
|
||||
if let error = error { throw error }
|
||||
return response
|
||||
|
||||
case "initExtensionRepo":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let cacheDir = args["cache_dir"] as! String
|
||||
@@ -1106,13 +1041,6 @@ import Gobackend
|
||||
if let error = error { throw error }
|
||||
return response
|
||||
|
||||
case "getExtensionBrowseCategories":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let extensionId = args["extension_id"] as! String
|
||||
let response = GobackendGetExtensionBrowseCategoriesJSON(extensionId, &error)
|
||||
if let error = error { throw error }
|
||||
return response
|
||||
|
||||
case "setLibraryCoverCacheDir":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let cacheDir = args["cache_dir"] as! String
|
||||
|
||||
@@ -501,11 +501,6 @@ class PlatformBridge {
|
||||
return response;
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> getDownloadProgress() async {
|
||||
final result = await _channel.invokeMethod('getDownloadProgress');
|
||||
return _decodeMapResult(result);
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> getAllDownloadProgress() async {
|
||||
final result = await _channel.invokeMethod('getAllDownloadProgress');
|
||||
return _decodeMapResult(result);
|
||||
@@ -521,14 +516,6 @@ class PlatformBridge {
|
||||
await _channel.invokeMethod('exitApp');
|
||||
}
|
||||
|
||||
static Future<void> initItemProgress(String itemId) async {
|
||||
await _channel.invokeMethod('initItemProgress', {'item_id': itemId});
|
||||
}
|
||||
|
||||
static Future<void> finishItemProgress(String itemId) async {
|
||||
await _channel.invokeMethod('finishItemProgress', {'item_id': itemId});
|
||||
}
|
||||
|
||||
static Future<void> clearItemProgress(String itemId) async {
|
||||
await _channel.invokeMethod('clearItemProgress', {'item_id': itemId});
|
||||
}
|
||||
@@ -578,13 +565,6 @@ class PlatformBridge {
|
||||
await _channel.invokeMethod('setAllowPrivateNetwork', {'allowed': allowed});
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> checkDuplicate(
|
||||
String outputDir,
|
||||
String isrc,
|
||||
) {
|
||||
return _invokeMap('checkDuplicate', {'output_dir': outputDir, 'isrc': isrc});
|
||||
}
|
||||
|
||||
static Future<String> buildFilename(
|
||||
String template,
|
||||
Map<String, dynamic> metadata,
|
||||
@@ -734,20 +714,6 @@ class PlatformBridge {
|
||||
return result as bool? ?? false;
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> fetchLyrics(
|
||||
String spotifyId,
|
||||
String trackName,
|
||||
String artistName, {
|
||||
int durationMs = 0,
|
||||
}) {
|
||||
return _invokeMap('fetchLyrics', {
|
||||
'spotify_id': spotifyId,
|
||||
'track_name': trackName,
|
||||
'artist_name': artistName,
|
||||
'duration_ms': durationMs,
|
||||
});
|
||||
}
|
||||
|
||||
static Future<String> getLyricsLRC(
|
||||
String spotifyId,
|
||||
String trackName,
|
||||
@@ -1111,16 +1077,6 @@ class PlatformBridge {
|
||||
await _channel.invokeMethod('clearTrackCache');
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> getDeezerRelatedArtists(
|
||||
String artistId, {
|
||||
int limit = 12,
|
||||
}) {
|
||||
return _invokeMap('getDeezerRelatedArtists', {
|
||||
'artist_id': artistId,
|
||||
'limit': limit,
|
||||
});
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> getProviderMetadata(
|
||||
String providerId,
|
||||
String resourceType,
|
||||
@@ -1198,11 +1154,6 @@ class PlatformBridge {
|
||||
);
|
||||
}
|
||||
|
||||
static Future<List<Map<String, dynamic>>> getGoLogs() async {
|
||||
final result = await _channel.invokeMethod('getLogs');
|
||||
return _decodeMapListResult(result, 'getGoLogs');
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> getGoLogsSince(int index) async {
|
||||
final result = await _channel.invokeMethod('getLogsSince', {
|
||||
'index': index,
|
||||
@@ -1395,18 +1346,6 @@ class PlatformBridge {
|
||||
return _decodeRequiredMapResult(result, 'invokeExtensionAction');
|
||||
}
|
||||
|
||||
static Future<List<Map<String, dynamic>>> searchTracksWithExtensions(
|
||||
String query, {
|
||||
int limit = 20,
|
||||
}) async {
|
||||
_log.d('searchTracksWithExtensions: "$query"');
|
||||
final result = await _channel.invokeMethod('searchTracksWithExtensions', {
|
||||
'query': query,
|
||||
'limit': limit,
|
||||
});
|
||||
return _decodeMapListResult(result, 'searchTracksWithExtensions');
|
||||
}
|
||||
|
||||
static Future<List<Map<String, dynamic>>> searchTracksWithMetadataProviders(
|
||||
String query, {
|
||||
int limit = 20,
|
||||
@@ -1602,11 +1541,6 @@ class PlatformBridge {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<Map<String, dynamic>>> getSearchProviders() async {
|
||||
final result = await _channel.invokeMethod('getSearchProviders');
|
||||
return _decodeMapListResult(result, 'getSearchProviders');
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>?> handleURLWithExtension(
|
||||
String url,
|
||||
) async {
|
||||
@@ -1727,9 +1661,22 @@ class PlatformBridge {
|
||||
return result as String;
|
||||
}
|
||||
|
||||
static Future<List<Map<String, dynamic>>> getURLHandlers() async {
|
||||
final result = await _channel.invokeMethod('getURLHandlers');
|
||||
return _decodeMapListResult(result, 'getURLHandlers');
|
||||
/// Streaming-platform links for a track, keyed by song.link platform ID.
|
||||
/// Served from the Go-side memory cache; throws when the lookup fails.
|
||||
static Future<Map<String, String>> getTrackPlatformLinks({
|
||||
String spotifyId = '',
|
||||
String isrc = '',
|
||||
}) async {
|
||||
final result = await _channel.invokeMethod('getTrackPlatformLinks', {
|
||||
'spotify_id': spotifyId,
|
||||
'isrc': isrc,
|
||||
});
|
||||
final decoded = _decodeMapResult(result);
|
||||
final platforms = decoded['platforms'];
|
||||
if (platforms is! Map) return const {};
|
||||
return platforms.map(
|
||||
(key, value) => MapEntry(key.toString(), value.toString()),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>?> getExtensionHomeFeed(
|
||||
@@ -1774,21 +1721,6 @@ class PlatformBridge {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>?> getExtensionBrowseCategories(
|
||||
String extensionId,
|
||||
) async {
|
||||
try {
|
||||
final result = await _channel.invokeMethod(
|
||||
'getExtensionBrowseCategories',
|
||||
{'extension_id': extensionId},
|
||||
);
|
||||
return _decodeNullableMapResult(result, 'getExtensionBrowseCategories');
|
||||
} catch (e) {
|
||||
_log.e('getExtensionBrowseCategories failed: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> setLibraryCoverCacheDir(String cacheDir) async {
|
||||
_log.i('setLibraryCoverCacheDir: $cacheDir');
|
||||
await _channel.invokeMethod('setLibraryCoverCacheDir', {
|
||||
@@ -2107,16 +2039,6 @@ class PlatformBridge {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> runPostProcessing(
|
||||
String filePath, {
|
||||
Map<String, dynamic>? metadata,
|
||||
}) {
|
||||
return _invokeMap('runPostProcessing', {
|
||||
'file_path': filePath,
|
||||
'metadata': metadata != null ? jsonEncode(metadata) : '',
|
||||
});
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> runPostProcessingV2(
|
||||
String filePath, {
|
||||
Map<String, dynamic>? metadata,
|
||||
@@ -2133,11 +2055,6 @@ class PlatformBridge {
|
||||
});
|
||||
}
|
||||
|
||||
static Future<List<Map<String, dynamic>>> getPostProcessingProviders() async {
|
||||
final result = await _channel.invokeMethod('getPostProcessingProviders');
|
||||
return _decodeMapListResult(result, 'getPostProcessingProviders');
|
||||
}
|
||||
|
||||
static Future<void> initExtensionRepo(String cacheDir) async {
|
||||
_log.d('initExtensionRepo: $cacheDir');
|
||||
await _channel.invokeMethod('initExtensionRepo', {'cache_dir': cacheDir});
|
||||
|
||||
Reference in New Issue
Block a user