diff --git a/go_backend/amazon.go b/go_backend/amazon.go index 39ebe11d..d15d293f 100644 --- a/go_backend/amazon.go +++ b/go_backend/amazon.go @@ -60,12 +60,10 @@ func amazonArtistsMatch(expectedArtist, foundArtist string) bool { return true } - // Check if one contains the other if strings.Contains(normExpected, normFound) || strings.Contains(normFound, normExpected) { return true } - // Check first artist (before comma or feat) expectedFirst := strings.Split(normExpected, ",")[0] expectedFirst = strings.Split(expectedFirst, " feat")[0] expectedFirst = strings.Split(expectedFirst, " ft.")[0] @@ -80,7 +78,6 @@ func amazonArtistsMatch(expectedArtist, foundArtist string) bool { return true } - // Check if first artist is contained in the other if strings.Contains(expectedFirst, foundFirst) || strings.Contains(foundFirst, expectedFirst) { return true } @@ -127,7 +124,6 @@ func (a *AmazonDownloader) waitForRateLimit() { now := time.Now() - // Reset counter every minute if now.Sub(a.apiCallResetTime) >= time.Minute { a.apiCallCount = 0 a.apiCallResetTime = now @@ -155,7 +151,6 @@ func (a *AmazonDownloader) waitForRateLimit() { } } - // Update tracking a.lastAPICallTime = time.Now() a.apiCallCount++ } @@ -181,8 +176,6 @@ func (a *AmazonDownloader) downloadFromDoubleDoubleService(amazonURL, _ string) for _, region := range a.regions { GoLog("[Amazon] Trying region: %s...\n", region) - // Build base URL for DoubleDouble service - // Decode base64 service URL (same as PC) serviceBase, _ := base64.StdEncoding.DecodeString("aHR0cHM6Ly8=") // https:// serviceDomain, _ := base64.StdEncoding.DecodeString("LmRvdWJsZWRvdWJsZS50b3A=") // .doubledouble.top baseURL := fmt.Sprintf("%s%s%s", string(serviceBase), region, string(serviceDomain)) @@ -301,7 +294,6 @@ func (a *AmazonDownloader) downloadFromDoubleDoubleService(amazonURL, _ string) if status.Status == "done" { fmt.Println("\n[Amazon] Download ready!") - // Build download URL fileURL := status.URL if strings.HasPrefix(fileURL, "./") { fileURL = fmt.Sprintf("%s/%s", baseURL, fileURL[2:]) @@ -383,7 +375,6 @@ func (a *AmazonDownloader) DownloadFile(downloadURL, outputPath, itemID string) } expectedSize := resp.ContentLength - // Set total bytes if available if expectedSize > 0 && itemID != "" { SetItemBytesTotal(itemID, expectedSize) } @@ -393,16 +384,13 @@ func (a *AmazonDownloader) DownloadFile(downloadURL, outputPath, itemID string) return err } - // Use buffered writer for better performance (256KB buffer) bufWriter := bufio.NewWriterSize(out, 256*1024) - // Use item progress writer with buffered output var written int64 if itemID != "" { pw := NewItemProgressWriter(bufWriter, itemID) written, err = io.Copy(pw, resp.Body) } else { - // Fallback: direct copy without progress tracking written, err = io.Copy(bufWriter, resp.Body) } @@ -410,7 +398,6 @@ func (a *AmazonDownloader) DownloadFile(downloadURL, outputPath, itemID string) flushErr := bufWriter.Flush() closeErr := out.Close() - // Check for any errors if err != nil { os.Remove(outputPath) if isDownloadCancelled(itemID) { @@ -456,24 +443,19 @@ type AmazonDownloadResult struct { func downloadFromAmazon(req DownloadRequest) (AmazonDownloadResult, error) { downloader := NewAmazonDownloader() - // Check for existing file first if existingFile, exists := checkISRCExistsInternal(req.OutputDir, req.ISRC); exists { return AmazonDownloadResult{FilePath: "EXISTS:" + existingFile}, nil } - // Get Amazon URL from SongLink songlink := NewSongLinkClient() var availability *TrackAvailability var err error - // Check if SpotifyID is actually a Deezer ID (format: "deezer:xxxxx") if strings.HasPrefix(req.SpotifyID, "deezer:") { - // Extract Deezer ID and use Deezer-based lookup deezerID := strings.TrimPrefix(req.SpotifyID, "deezer:") GoLog("[Amazon] Using Deezer ID for SongLink lookup: %s\n", deezerID) availability, err = songlink.CheckAvailabilityFromDeezer(deezerID) } else if req.SpotifyID != "" { - // Use Spotify ID availability, err = songlink.CheckTrackAvailability(req.SpotifyID, req.ISRC) } else { return AmazonDownloadResult{}, fmt.Errorf("no valid Spotify or Deezer ID provided for Amazon lookup") @@ -487,7 +469,6 @@ func downloadFromAmazon(req DownloadRequest) (AmazonDownloadResult, error) { return AmazonDownloadResult{}, fmt.Errorf("track not available on Amazon Music (SongLink returned no Amazon URL)") } - // Create output directory if needed if req.OutputDir != "." { if err := os.MkdirAll(req.OutputDir, 0755); err != nil { return AmazonDownloadResult{}, fmt.Errorf("failed to create output directory: %w", err) @@ -506,10 +487,8 @@ func downloadFromAmazon(req DownloadRequest) (AmazonDownloadResult, error) { return AmazonDownloadResult{}, fmt.Errorf("artist mismatch: expected '%s', got '%s'", req.ArtistName, artistName) } - // Log match found GoLog("[Amazon] Match found: '%s' by '%s'\n", trackName, artistName) - // Build filename using Spotify metadata (more accurate) filename := buildFilenameFromTemplate(req.FilenameFormat, map[string]interface{}{ "title": req.TrackName, "artist": req.ArtistName, @@ -521,7 +500,6 @@ func downloadFromAmazon(req DownloadRequest) (AmazonDownloadResult, error) { filename = sanitizeFilename(filename) + ".flac" outputPath := filepath.Join(req.OutputDir, filename) - // Check if file already exists if fileInfo, statErr := os.Stat(outputPath); statErr == nil && fileInfo.Size() > 0 { return AmazonDownloadResult{FilePath: "EXISTS:" + outputPath}, nil } @@ -552,8 +530,6 @@ func downloadFromAmazon(req DownloadRequest) (AmazonDownloadResult, error) { // Wait for parallel operations to complete <-parallelDone - // Set progress to 100% and status to finalizing (before embedding) - // This makes the UI show "Finalizing..." while embedding happens if req.ItemID != "" { SetItemProgress(req.ItemID, 1.0, 0, 0) SetItemFinalizing(req.ItemID) @@ -564,14 +540,11 @@ func downloadFromAmazon(req DownloadRequest) (AmazonDownloadResult, error) { GoLog("[Amazon] DoubleDouble returned: %s - %s\n", artistName, trackName) } - // Read existing metadata from downloaded file BEFORE embedding - // Amazon/DoubleDouble files often have correct track/disc numbers that we should preserve existingMeta, metaErr := ReadMetadata(outputPath) actualTrackNum := req.TrackNumber actualDiscNum := req.DiscNumber if metaErr == nil && existingMeta != nil { - // Use file metadata if it has valid track/disc numbers and request doesn't have them if existingMeta.TrackNumber > 0 && (req.TrackNumber == 0 || req.TrackNumber == 1) { actualTrackNum = existingMeta.TrackNumber GoLog("[Amazon] Using track number from file: %d (request had: %d)\n", actualTrackNum, req.TrackNumber) @@ -621,8 +594,6 @@ func downloadFromAmazon(req DownloadRequest) (AmazonDownloadResult, error) { fmt.Println("[Amazon] ✓ Downloaded successfully from Amazon Music") - // Read actual quality from the downloaded FLAC file - // Amazon API doesn't provide quality info, but we can read it from the file itself quality, err := GetAudioQuality(outputPath) if err != nil { GoLog("[Amazon] Warning: couldn't read quality from file: %v\n", err) @@ -630,8 +601,6 @@ func downloadFromAmazon(req DownloadRequest) (AmazonDownloadResult, error) { GoLog("[Amazon] Actual quality: %d-bit/%dHz\n", quality.BitDepth, quality.SampleRate) } - // Read metadata from file AFTER embedding to get accurate values - // This ensures we return what's actually in the file finalMeta, metaReadErr := ReadMetadata(outputPath) if metaReadErr == nil && finalMeta != nil { GoLog("[Amazon] Final metadata from file - Track: %d, Disc: %d, Date: %s\n", @@ -639,7 +608,6 @@ func downloadFromAmazon(req DownloadRequest) (AmazonDownloadResult, error) { actualTrackNum = finalMeta.TrackNumber actualDiscNum = finalMeta.DiscNumber if finalMeta.Date != "" { - // Use date from file if available req.ReleaseDate = finalMeta.Date } } diff --git a/go_backend/deezer.go b/go_backend/deezer.go index 38dae095..6c88c529 100644 --- a/go_backend/deezer.go +++ b/go_backend/deezer.go @@ -113,7 +113,6 @@ func (c *DeezerClient) convertTrack(track deezerTrack) TrackMetadata { albumImage = track.Album.Cover } - // Try to find release date releaseDate := track.ReleaseDate if releaseDate == "" { releaseDate = track.Album.ReleaseDate @@ -541,7 +540,6 @@ func (c *DeezerClient) SearchByISRC(ctx context.Context, isrc string) (*TrackMet return &result, nil } - // Check if we got a valid response (ID > 0) if track.ID == 0 { return nil, fmt.Errorf("no track found for ISRC: %s", isrc) } @@ -564,7 +562,6 @@ func (c *DeezerClient) fetchISRCsParallel(ctx context.Context, tracks []deezerTr result := make(map[string]string) var resultMu sync.Mutex - // First, check cache for existing ISRCs var tracksToFetch []deezerTrack c.cacheMu.RLock() for _, track := range tracks { @@ -622,7 +619,6 @@ func (c *DeezerClient) fetchISRCsParallel(ctx context.Context, tracks []deezerTr // GetTrackISRC fetches ISRC for a single track (with caching) // Use this when you need ISRC for download func (c *DeezerClient) GetTrackISRC(ctx context.Context, trackID string) (string, error) { - // Check cache first c.cacheMu.RLock() if isrc, ok := c.isrcCache[trackID]; ok { c.cacheMu.RUnlock() diff --git a/go_backend/duplicate.go b/go_backend/duplicate.go index 48b53299..bcfc3fcb 100644 --- a/go_backend/duplicate.go +++ b/go_backend/duplicate.go @@ -36,7 +36,6 @@ func GetISRCIndex(outputDir string) *ISRCIndex { return idx } - // Build new index return buildISRCIndex(outputDir) } @@ -56,7 +55,6 @@ func buildISRCIndex(outputDir string) *ISRCIndex { startTime := time.Now() fileCount := 0 - // Walk directory - only check .flac files filepath.Walk(outputDir, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() { return nil @@ -67,13 +65,11 @@ func buildISRCIndex(outputDir string) *ISRCIndex { return nil } - // Read ISRC from file metadata, err := ReadMetadata(path) if err != nil || metadata.ISRC == "" { return nil } - // Store in index (uppercase for case-insensitive matching) idx.index[strings.ToUpper(metadata.ISRC)] = path fileCount++ return nil @@ -82,7 +78,6 @@ func buildISRCIndex(outputDir string) *ISRCIndex { fmt.Printf("[ISRCIndex] Built index for %s: %d files in %v\n", outputDir, fileCount, time.Since(startTime).Round(time.Millisecond)) - // Cache the index isrcIndexCacheMu.Lock() isrcIndexCache[outputDir] = idx isrcIndexCacheMu.Unlock() @@ -205,10 +200,8 @@ func CheckFilesExistParallel(outputDir string, tracksJSON string) (string, error results := make([]FileExistenceResult, len(tracks)) - // Build ISRC index from output directory (scan once) isrcIdx := GetISRCIndex(outputDir) - // Check each track against the index (parallel) var wg sync.WaitGroup for i, track := range tracks { wg.Add(1) diff --git a/go_backend/exports.go b/go_backend/exports.go index 76972991..21a9ec6f 100644 --- a/go_backend/exports.go +++ b/go_backend/exports.go @@ -283,10 +283,8 @@ func DownloadTrack(requestJSON string) (string, error) { return errorResponse(err.Error()) } - // Check if file already exists if len(result.FilePath) > 7 && result.FilePath[:7] == "EXISTS:" { actualPath := result.FilePath[7:] - // Read actual quality from existing file quality, qErr := GetAudioQuality(actualPath) if qErr == nil { result.BitDepth = quality.BitDepth @@ -312,7 +310,6 @@ func DownloadTrack(requestJSON string) (string, error) { return string(jsonBytes), nil } - // Read actual quality from downloaded file (more accurate than API) quality, qErr := GetAudioQuality(result.FilePath) if qErr == nil { result.BitDepth = quality.BitDepth @@ -362,7 +359,6 @@ func DownloadWithFallback(requestJSON string) (string, error) { AddAllowedDownloadDir(req.OutputDir) } - // Build service order starting with preferred service allServices := []string{"tidal", "qobuz", "amazon"} preferredService := req.Service if preferredService == "" { @@ -371,7 +367,6 @@ func DownloadWithFallback(requestJSON string) (string, error) { GoLog("[DownloadWithFallback] Preferred service from request: '%s'\n", req.Service) - // Create ordered list: preferred first, then others services := []string{preferredService} for _, s := range allServices { if s != preferredService { @@ -455,10 +450,8 @@ func DownloadWithFallback(requestJSON string) (string, error) { } if err == nil { - // Check if file already exists if len(result.FilePath) > 7 && result.FilePath[:7] == "EXISTS:" { actualPath := result.FilePath[7:] - // Read actual quality from existing file quality, qErr := GetAudioQuality(actualPath) if qErr == nil { result.BitDepth = quality.BitDepth @@ -484,7 +477,6 @@ func DownloadWithFallback(requestJSON string) (string, error) { return string(jsonBytes), nil } - // Read actual quality from downloaded file (more accurate than API) quality, qErr := GetAudioQuality(result.FilePath) if qErr == nil { result.BitDepth = quality.BitDepth @@ -567,10 +559,8 @@ func ReadFileMetadata(filePath string) (string, error) { return "", fmt.Errorf("failed to read metadata: %w", err) } - // Also get audio quality info quality, qualityErr := GetAudioQuality(filePath) - // Get duration from FLAC stream info duration := 0 if qualityErr == nil && quality.SampleRate > 0 && quality.TotalSamples > 0 { duration = int(quality.TotalSamples / int64(quality.SampleRate)) @@ -640,7 +630,6 @@ func PreBuildDuplicateIndex(outputDir string) error { } // InvalidateDuplicateIndex clears the ISRC index cache for a directory -// Call this when files are deleted or moved func InvalidateDuplicateIndex(outputDir string) { InvalidateISRCCache(outputDir) } @@ -703,7 +692,6 @@ func GetLyricsLRC(spotifyID, trackName, artistName string, filePath string) (str return "", err } - // Convert to LRC format with metadata headers (like PC version) lrcContent := convertToLRCWithMetadata(lyricsData, trackName, artistName) return lrcContent, nil } @@ -740,7 +728,6 @@ func PreWarmTrackCacheJSON(tracksJSON string) (string, error) { return errorResponse("Invalid JSON: " + err.Error()) } - // Convert to PreWarmCacheRequest requests := make([]PreWarmCacheRequest, len(tracks)) for i, t := range tracks { requests[i] = PreWarmCacheRequest{ @@ -872,7 +859,6 @@ func SearchDeezerByISRC(isrc string) (string, error) { } // ConvertSpotifyToDeezer converts a Spotify track/album ID to Deezer and fetches metadata -// This uses SongLink API to find the Deezer equivalent, then fetches from Deezer // Useful when Spotify API is rate limited func ConvertSpotifyToDeezer(resourceType, spotifyID string) (string, error) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) @@ -881,7 +867,6 @@ func ConvertSpotifyToDeezer(resourceType, spotifyID string) (string, error) { songlink := NewSongLinkClient() deezerClient := GetDeezerClient() - // For tracks, we can use SongLink to get Deezer ID if resourceType == "track" { deezerID, err := songlink.GetDeezerIDFromSpotify(spotifyID) if err != nil { @@ -902,7 +887,6 @@ func ConvertSpotifyToDeezer(resourceType, spotifyID string) (string, error) { return string(jsonBytes), nil } - // For albums, SongLink also provides mapping if resourceType == "album" { deezerID, err := songlink.GetDeezerAlbumIDFromSpotify(spotifyID) if err != nil { @@ -947,7 +931,6 @@ func GetSpotifyMetadataWithDeezerFallback(spotifyURL string) (string, error) { return string(jsonBytes), nil } - // Check if it's a rate limit error errStr := strings.ToLower(err.Error()) if !strings.Contains(errStr, "429") && !strings.Contains(errStr, "rate") && !strings.Contains(errStr, "limit") { // Not a rate limit error, return original error @@ -964,7 +947,6 @@ func GetSpotifyMetadataWithDeezerFallback(spotifyURL string) (string, error) { GoLog("[Fallback] Spotify rate limited for %s, trying Deezer...\n", parsed.Type) if parsed.Type == "track" || parsed.Type == "album" { - // Convert to Deezer return ConvertSpotifyToDeezer(parsed.Type, parsed.ID) } @@ -1372,7 +1354,6 @@ func IsExtensionAuthenticatedByID(extensionID string) bool { return false } - // Check if token is expired if state.IsAuthenticated && !state.ExpiresAt.IsZero() && time.Now().After(state.ExpiresAt) { return false } @@ -1518,7 +1499,6 @@ func CustomSearchWithExtensionJSON(extensionID, query string, optionsJSON string return "", err } - // Convert to map format for Flutter, ensuring images field is set result := make([]map[string]interface{}, len(tracks)) for i, track := range tracks { result[i] = map[string]interface{}{ @@ -1585,12 +1565,10 @@ func HandleURLWithExtensionJSON(url string) (string, error) { result := resultWithID.Result extensionID := resultWithID.ExtensionID - // Check if result is nil (handler found but returned error) if result == nil { return "", fmt.Errorf("extension %s failed to handle URL", extensionID) } - // Build response response := map[string]interface{}{ "type": result.Type, "extension_id": extensionID, @@ -1758,10 +1736,8 @@ func GetAlbumWithExtensionJSON(extensionID, albumID string) (string, error) { return "", fmt.Errorf("album not found") } - // Convert tracks to map format tracks := make([]map[string]interface{}, len(album.Tracks)) for i, track := range album.Tracks { - // Use album cover as fallback if track doesn't have its own cover trackCover := track.ResolvedCoverURL() if trackCover == "" { trackCover = album.CoverURL @@ -1818,7 +1794,6 @@ func GetPlaylistWithExtensionJSON(extensionID, playlistID string) (string, error provider := NewExtensionProviderWrapper(ext) - // Try getPlaylist first, fall back to getAlbum (some extensions use album for playlists) script := fmt.Sprintf(` (function() { if (typeof extension !== 'undefined' && typeof extension.getPlaylist === 'function') { @@ -1856,10 +1831,8 @@ func GetPlaylistWithExtensionJSON(extensionID, playlistID string) (string, error album.Tracks[i].ProviderID = ext.ID } - // Convert tracks to map format tracks := make([]map[string]interface{}, len(album.Tracks)) for i, track := range album.Tracks { - // Use playlist cover as fallback if track doesn't have its own cover trackCover := track.ResolvedCoverURL() if trackCover == "" { trackCover = album.CoverURL @@ -1922,7 +1895,6 @@ func GetArtistWithExtensionJSON(extensionID, artistID string) (string, error) { return "", fmt.Errorf("artist not found") } - // Convert albums to map format albums := make([]map[string]interface{}, len(artist.Albums)) for i, album := range artist.Albums { albums[i] = map[string]interface{}{ diff --git a/go_backend/extension_manager.go b/go_backend/extension_manager.go index df64b8dc..52bdc78a 100644 --- a/go_backend/extension_manager.go +++ b/go_backend/extension_manager.go @@ -92,7 +92,6 @@ func (m *ExtensionManager) SetDirectories(extensionsDir, dataDir string) error { m.extensionsDir = extensionsDir m.dataDir = dataDir - // Create directories if they don't exist if err := os.MkdirAll(extensionsDir, 0755); err != nil { return fmt.Errorf("failed to create extensions directory: %w", err) } @@ -117,7 +116,6 @@ func (m *ExtensionManager) LoadExtensionFromFile(filePath string) (*LoadedExtens } defer zipReader.Close() - // Find and read manifest.json var manifestData []byte var hasIndexJS bool for _, file := range zipReader.File { @@ -146,13 +144,11 @@ func (m *ExtensionManager) LoadExtensionFromFile(filePath string) (*LoadedExtens return nil, fmt.Errorf("Invalid extension package: index.js not found") } - // Parse and validate manifest manifest, err := ParseManifest(manifestData) if err != nil { return nil, fmt.Errorf("Invalid extension manifest: %w", err) } - // Check if extension already loaded - if so, try upgrade (check without holding lock for long) m.mu.RLock() existing, exists := m.extensions[manifest.Name] var existingVersion string @@ -164,7 +160,6 @@ func (m *ExtensionManager) LoadExtensionFromFile(filePath string) (*LoadedExtens m.mu.RUnlock() if exists { - // Check if this is an upgrade versionCompare := compareVersions(manifest.Version, existingVersion) if versionCompare > 0 { // This is an upgrade - call UpgradeExtension @@ -176,16 +171,13 @@ func (m *ExtensionManager) LoadExtensionFromFile(filePath string) (*LoadedExtens } } - // Now acquire write lock for the rest of the operation m.mu.Lock() defer m.mu.Unlock() - // Double-check extension wasn't added while we were waiting for lock if _, exists := m.extensions[manifest.Name]; exists { return nil, fmt.Errorf("Extension '%s' was installed by another process", manifest.DisplayName) } - // Create extension directory extDir := filepath.Join(m.extensionsDir, manifest.Name) if err := os.MkdirAll(extDir, 0755); err != nil { return nil, fmt.Errorf("failed to create extension directory: %w", err) @@ -206,19 +198,16 @@ func (m *ExtensionManager) LoadExtensionFromFile(filePath string) (*LoadedExtens } destPath := filepath.Join(extDir, relPath) - // Create parent directories if needed destDir := filepath.Dir(destPath) if err := os.MkdirAll(destDir, 0755); err != nil { return nil, fmt.Errorf("failed to create directory %s: %w", destDir, err) } - // Create destination file destFile, err := os.Create(destPath) if err != nil { return nil, fmt.Errorf("failed to create file %s: %w", destPath, err) } - // Copy content srcFile, err := file.Open() if err != nil { destFile.Close() @@ -233,13 +222,11 @@ func (m *ExtensionManager) LoadExtensionFromFile(filePath string) (*LoadedExtens } } - // Create data directory for extension extDataDir := filepath.Join(m.dataDir, manifest.Name) if err := os.MkdirAll(extDataDir, 0755); err != nil { return nil, fmt.Errorf("failed to create extension data directory: %w", err) } - // Create loaded extension ext := &LoadedExtension{ ID: manifest.Name, Manifest: manifest, @@ -263,23 +250,19 @@ func (m *ExtensionManager) LoadExtensionFromFile(filePath string) (*LoadedExtens // initializeVM creates and initializes the Goja VM for an extension func (m *ExtensionManager) initializeVM(ext *LoadedExtension) error { - // Create new Goja runtime vm := goja.New() ext.VM = vm - // Read index.js indexPath := filepath.Join(ext.SourceDir, "index.js") jsCode, err := os.ReadFile(indexPath) if err != nil { return fmt.Errorf("failed to read index.js: %w", err) } - // Create extension runtime and register sandboxed APIs runtime := NewExtensionRuntime(ext) runtime.RegisterAPIs(vm) runtime.RegisterGoBackendAPIs(vm) - // Set up console.log for debugging console := vm.NewObject() console.Set("log", func(call goja.FunctionCall) goja.Value { args := make([]interface{}, len(call.Arguments)) @@ -291,12 +274,10 @@ func (m *ExtensionManager) initializeVM(ext *LoadedExtension) error { }) vm.Set("console", console) - // Set up registerExtension function var registeredExtension goja.Value vm.Set("registerExtension", func(call goja.FunctionCall) goja.Value { if len(call.Arguments) > 0 { registeredExtension = call.Arguments[0] - // Also set it as global 'extension' variable for later access vm.Set("extension", call.Arguments[0]) } return goja.Undefined() @@ -406,7 +387,6 @@ func (m *ExtensionManager) LoadExtensionsFromDirectory(dirPath string) ([]string for _, entry := range entries { if entry.IsDir() { - // Check if it's an extracted extension directory manifestPath := filepath.Join(dirPath, entry.Name(), "manifest.json") if _, err := os.Stat(manifestPath); err == nil { ext, err := m.loadExtensionFromDirectory(filepath.Join(dirPath, entry.Name())) @@ -418,7 +398,6 @@ func (m *ExtensionManager) LoadExtensionsFromDirectory(dirPath string) ([]string } } } else if strings.HasSuffix(strings.ToLower(entry.Name()), ".spotiflac-ext") { - // Load from package file ext, err := m.LoadExtensionFromFile(filepath.Join(dirPath, entry.Name())) if err != nil { GoLog("[Extension] Failed to load %s: %v\n", entry.Name(), err) @@ -437,7 +416,6 @@ func (m *ExtensionManager) loadExtensionFromDirectory(dirPath string) (*LoadedEx m.mu.Lock() defer m.mu.Unlock() - // Read manifest manifestPath := filepath.Join(dirPath, "manifest.json") manifestData, err := os.ReadFile(manifestPath) if err != nil { @@ -450,25 +428,21 @@ func (m *ExtensionManager) loadExtensionFromDirectory(dirPath string) (*LoadedEx return nil, fmt.Errorf("Invalid extension manifest: %w", err) } - // Check if index.js exists indexPath := filepath.Join(dirPath, "index.js") if _, err := os.Stat(indexPath); os.IsNotExist(err) { return nil, fmt.Errorf("Extension is missing index.js file") } - // Check if extension already loaded - skip silently (for directory loading on startup) if existing, exists := m.extensions[manifest.Name]; exists { GoLog("[Extension] Extension '%s' already loaded, skipping\n", manifest.DisplayName) return existing, nil } - // Create data directory for extension extDataDir := filepath.Join(m.dataDir, manifest.Name) if err := os.MkdirAll(extDataDir, 0755); err != nil { return nil, fmt.Errorf("failed to create extension data directory: %w", err) } - // Create loaded extension ext := &LoadedExtension{ ID: manifest.Name, Manifest: manifest, @@ -541,7 +515,6 @@ func (m *ExtensionManager) UpgradeExtension(filePath string) (*LoadedExtension, } defer zipReader.Close() - // Find and read manifest.json var manifestData []byte var hasIndexJS bool for _, file := range zipReader.File { @@ -570,13 +543,11 @@ func (m *ExtensionManager) UpgradeExtension(filePath string) (*LoadedExtension, return nil, fmt.Errorf("Invalid extension package: index.js not found") } - // Parse and validate manifest newManifest, err := ParseManifest(manifestData) if err != nil { return nil, fmt.Errorf("Invalid extension manifest: %w", err) } - // Check if extension exists m.mu.RLock() existing, exists := m.extensions[newManifest.Name] m.mu.RUnlock() @@ -612,19 +583,15 @@ func (m *ExtensionManager) UpgradeExtension(filePath string) (*LoadedExtension, } } - // Recreate extension directory if err := os.MkdirAll(extDir, 0755); err != nil { return nil, fmt.Errorf("failed to create extension directory: %w", err) } - // Extract all files from new package (preserving directory structure) for _, file := range zipReader.File { if file.FileInfo().IsDir() { continue } - // Preserve relative path within the zip (support subdirectories) - // Clean the path to prevent path traversal attacks relPath := filepath.Clean(file.Name) if strings.HasPrefix(relPath, "..") || filepath.IsAbs(relPath) { GoLog("[Extension] Skipping unsafe path in archive: %s\n", file.Name) @@ -632,19 +599,16 @@ func (m *ExtensionManager) UpgradeExtension(filePath string) (*LoadedExtension, } destPath := filepath.Join(extDir, relPath) - // Create parent directories if needed destDir := filepath.Dir(destPath) if err := os.MkdirAll(destDir, 0755); err != nil { return nil, fmt.Errorf("failed to create directory %s: %w", destDir, err) } - // Create destination file destFile, err := os.Create(destPath) if err != nil { return nil, fmt.Errorf("failed to create file %s: %w", destPath, err) } - // Copy content srcFile, err := file.Open() if err != nil { destFile.Close() @@ -659,7 +623,6 @@ func (m *ExtensionManager) UpgradeExtension(filePath string) (*LoadedExtension, } } - // Create new loaded extension (reusing data directory, preserving enabled state) ext := &LoadedExtension{ ID: newManifest.Name, Manifest: newManifest, @@ -708,7 +671,6 @@ func (m *ExtensionManager) checkExtensionUpgradeInternal(filePath string) (*Exte } defer zipReader.Close() - // Find and read manifest.json var manifestData []byte for _, file := range zipReader.File { name := filepath.Base(file.Name) @@ -730,13 +692,11 @@ func (m *ExtensionManager) checkExtensionUpgradeInternal(filePath string) (*Exte return nil, fmt.Errorf("manifest.json not found") } - // Parse manifest newManifest, err := ParseManifest(manifestData) if err != nil { return nil, fmt.Errorf("Invalid manifest: %w", err) } - // Check if extension exists m.mu.RLock() existing, exists := m.extensions[newManifest.Name] m.mu.RUnlock() @@ -752,7 +712,6 @@ func (m *ExtensionManager) checkExtensionUpgradeInternal(filePath string) (*Exte info.CurrentVersion = "" info.CanUpgrade = false } else { - // Compare versions info.CurrentVersion = existing.Manifest.Version info.CanUpgrade = compareVersions(newManifest.Version, existing.Manifest.Version) > 0 } @@ -805,7 +764,6 @@ func (m *ExtensionManager) GetInstalledExtensionsJSON() (string, error) { infos := make([]ExtensionInfo, len(extensions)) for i, ext := range extensions { - // Build permissions list permissions := []string{} for _, domain := range ext.Manifest.Permissions.Network { permissions = append(permissions, "network:"+domain) @@ -822,7 +780,6 @@ func (m *ExtensionManager) GetInstalledExtensionsJSON() (string, error) { status = "disabled" } - // Check for icon file iconPath := "" if ext.Manifest.Icon != "" && ext.SourceDir != "" { possibleIcon := filepath.Join(ext.SourceDir, ext.Manifest.Icon) @@ -830,7 +787,6 @@ func (m *ExtensionManager) GetInstalledExtensionsJSON() (string, error) { iconPath = possibleIcon } } - // Fallback: check for icon.png if not specified in manifest if iconPath == "" && ext.SourceDir != "" { possibleIcon := filepath.Join(ext.SourceDir, "icon.png") if _, err := os.Stat(possibleIcon); err == nil { @@ -887,13 +843,11 @@ func (m *ExtensionManager) InitializeExtension(extensionID string, settings map[ return fmt.Errorf("Extension failed to load. Please reinstall the extension") } - // Convert settings to JSON for passing to JS settingsJSON, err := json.Marshal(settings) if err != nil { return fmt.Errorf("Failed to save settings") } - // Call initialize function script := fmt.Sprintf(` (function() { var settings = %s; @@ -917,7 +871,6 @@ func (m *ExtensionManager) InitializeExtension(extensionID string, settings map[ return err } - // Check result if result != nil && !goja.IsUndefined(result) { exported := result.Export() if resultMap, ok := exported.(map[string]interface{}); ok { @@ -973,7 +926,6 @@ func (m *ExtensionManager) CleanupExtension(extensionID string) error { return err } - // Check result if result != nil && !goja.IsUndefined(result) { exported := result.Export() if resultMap, ok := exported.(map[string]interface{}); ok { diff --git a/go_backend/extension_providers.go b/go_backend/extension_providers.go index 688bbf31..9fe583ea 100644 --- a/go_backend/extension_providers.go +++ b/go_backend/extension_providers.go @@ -189,7 +189,6 @@ func (p *ExtensionProviderWrapper) SearchTracks(query string, limit int) (*ExtSe } } - // Set provider ID on all tracks for i := range searchResult.Tracks { searchResult.Tracks[i].ProviderID = p.extension.ID } @@ -737,12 +736,10 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro enrichedTrack, err := provider.EnrichTrack(trackMeta) if err == nil && enrichedTrack != nil { - // Update request with enriched data if enrichedTrack.ISRC != "" && enrichedTrack.ISRC != req.ISRC { GoLog("[DownloadWithExtensionFallback] ISRC enriched: %s -> %s\n", req.ISRC, enrichedTrack.ISRC) req.ISRC = enrichedTrack.ISRC } - // Update service-specific IDs from Odesli enrichment if enrichedTrack.TidalID != "" { GoLog("[DownloadWithExtensionFallback] Tidal ID from Odesli: %s\n", enrichedTrack.TidalID) req.TidalID = enrichedTrack.TidalID @@ -755,7 +752,6 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro GoLog("[DownloadWithExtensionFallback] Deezer ID from Odesli: %s\n", enrichedTrack.DeezerID) req.DeezerID = enrichedTrack.DeezerID } - // Can also update other fields if needed if enrichedTrack.Name != "" { req.TrackName = enrichedTrack.Name } @@ -772,7 +768,6 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro ext, err := extManager.GetExtension(req.Source) if err == nil && ext.Enabled && ext.Error == "" && ext.Manifest.IsDownloadProvider() { - // Check if this extension wants to skip built-in fallback skipBuiltIn = ext.Manifest.SkipBuiltInFallback provider := NewExtensionProviderWrapper(ext) @@ -783,7 +778,6 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro GoLog("[DownloadWithExtensionFallback] Downloading from source extension with trackID: %s (skipBuiltInFallback: %v)\n", trackID, skipBuiltIn) - // Build output path outputPath := buildOutputPath(req) // Download directly using the track ID from the extension @@ -916,7 +910,6 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro provider := NewExtensionProviderWrapper(ext) - // Check availability first availability, err := provider.CheckAvailability(req.ISRC, req.TrackName, req.ArtistName) if err != nil || !availability.Available { GoLog("[DownloadWithExtensionFallback] %s: not available\n", providerID) @@ -926,12 +919,9 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro continue } - // Build output path outputPath := buildOutputPath(req) - // Download result, err := provider.Download(availability.TrackID, req.Quality, outputPath, func(percent int) { - // Update progress if req.ItemID != "" { SetItemProgress(req.ItemID, float64(percent), 0, 0) } @@ -1171,7 +1161,6 @@ func (p *ExtensionProviderWrapper) CustomSearch(query string, options map[string tracks = []ExtTrackMetadata{} } - // Set provider ID on all tracks for i := range tracks { tracks[i].ProviderID = p.extension.ID } @@ -1255,7 +1244,6 @@ func (p *ExtensionProviderWrapper) HandleURL(url string) (*ExtURLHandleResult, e handleResult.Artist.Albums[i].Tracks[j].ProviderID = p.extension.ID } } - // Set provider ID on top tracks for i := range handleResult.Artist.TopTracks { handleResult.Artist.TopTracks[i].ProviderID = p.extension.ID } @@ -1493,12 +1481,10 @@ func (m *ExtensionManager) RunPostProcessing(filePath string, metadata map[strin for _, provider := range providers { hooks := provider.extension.Manifest.GetPostProcessingHooks() for _, hook := range hooks { - // Check if hook is enabled (TODO: check user settings) if !hook.DefaultEnabled { continue } - // Check if format is supported ext := strings.ToLower(filepath.Ext(currentPath)) if len(hook.SupportedFormats) > 0 { supported := false diff --git a/go_backend/logbuffer.go b/go_backend/logbuffer.go index fafadccf..87820614 100644 --- a/go_backend/logbuffer.go +++ b/go_backend/logbuffer.go @@ -73,12 +73,10 @@ func (lb *LogBuffer) Add(level, tag, message string) { } if len(lb.entries) >= lb.maxSize { - // Remove oldest entry lb.entries = lb.entries[1:] } lb.entries = append(lb.entries, entry) - // Also print to logcat for debugging fmt.Printf("[%s] %s\n", tag, message) } diff --git a/go_backend/metadata.go b/go_backend/metadata.go index e026aa33..25f09dac 100644 --- a/go_backend/metadata.go +++ b/go_backend/metadata.go @@ -51,7 +51,6 @@ func EmbedMetadata(filePath string, metadata Metadata, coverPath string) error { cmt = flacvorbis.New() } - // Set metadata fields setComment(cmt, "TITLE", metadata.Title) setComment(cmt, "ARTIST", metadata.Artist) setComment(cmt, "ALBUM", metadata.Album) @@ -83,7 +82,6 @@ func EmbedMetadata(filePath string, metadata Metadata, coverPath string) error { setComment(cmt, "UNSYNCEDLYRICS", metadata.Lyrics) } - // Update or add vorbis comment block cmtBlock := cmt.Marshal() if cmtIdx >= 0 { f.Meta[cmtIdx] = &cmtBlock @@ -151,7 +149,6 @@ func EmbedMetadataWithCoverData(filePath string, metadata Metadata, coverData [] cmt = flacvorbis.New() } - // Set metadata fields setComment(cmt, "TITLE", metadata.Title) setComment(cmt, "ARTIST", metadata.Artist) setComment(cmt, "ALBUM", metadata.Album) @@ -183,7 +180,6 @@ func EmbedMetadataWithCoverData(filePath string, metadata Metadata, coverData [] setComment(cmt, "UNSYNCEDLYRICS", metadata.Lyrics) } - // Update or add vorbis comment block cmtBlock := cmt.Marshal() if cmtIdx >= 0 { f.Meta[cmtIdx] = &cmtBlock @@ -309,7 +305,6 @@ func getComment(cmt *flacvorbis.MetaDataBlockVorbisComment, key string) string { return "" } -// fileExists checks if a file exists func fileExists(path string) bool { _, err := os.Stat(path) return err == nil @@ -367,13 +362,11 @@ func ExtractLyrics(filePath string) (string, error) { continue } - // Try LYRICS tag first lyrics, err := cmt.Get("LYRICS") if err == nil && len(lyrics) > 0 && lyrics[0] != "" { return lyrics[0], nil } - // Fallback to UNSYNCEDLYRICS lyrics, err = cmt.Get("UNSYNCEDLYRICS") if err == nil && len(lyrics) > 0 && lyrics[0] != "" { return lyrics[0], nil @@ -406,10 +399,7 @@ func GetAudioQuality(filePath string) (AudioQuality, error) { return AudioQuality{}, fmt.Errorf("failed to read marker: %w", err) } - // Check if it's a FLAC file if string(marker) == "fLaC" { - // Continue reading FLAC metadata - // Read metadata block header (4 bytes) header := make([]byte, 4) if _, err := file.Read(header); err != nil { return AudioQuality{}, fmt.Errorf("failed to read header: %w", err) @@ -420,7 +410,6 @@ func GetAudioQuality(filePath string) (AudioQuality, error) { return AudioQuality{}, fmt.Errorf("first block is not STREAMINFO") } - // Read STREAMINFO block (34 bytes minimum) streamInfo := make([]byte, 34) if _, err := file.Read(streamInfo); err != nil { return AudioQuality{}, fmt.Errorf("failed to read STREAMINFO: %w", err) @@ -468,7 +457,6 @@ func EmbedM4AMetadata(filePath string, metadata Metadata, coverData []byte) erro return fmt.Errorf("failed to read M4A file: %w", err) } - // Find moov atom position moovPos := findAtom(data, "moov", 0) if moovPos < 0 { return fmt.Errorf("moov atom not found in M4A file") @@ -481,7 +469,6 @@ func EmbedM4AMetadata(filePath string, metadata Metadata, coverData []byte) erro var newData []byte if udtaPos >= 0 && udtaPos < moovPos+moovSize { - // udta exists, find meta inside it or replace udtaSize := int(uint32(data[udtaPos])<<24 | uint32(data[udtaPos+1])<<16 | uint32(data[udtaPos+2])<<8 | uint32(data[udtaPos+3])) metaPos := findAtom(data, "meta", udtaPos+8) @@ -522,7 +509,6 @@ func EmbedM4AMetadata(filePath string, metadata Metadata, coverData []byte) erro newData = append(newData, data[insertPos:]...) } - // Update moov size newMoovSize := moovSize + len(newData) - len(data) newData[moovPos] = byte(newMoovSize >> 24) newData[moovPos+1] = byte(newMoovSize >> 16) @@ -557,52 +543,42 @@ func findAtom(data []byte, name string, offset int) int { func buildMetaAtom(metadata Metadata, coverData []byte) []byte { var ilst []byte - // ©nam - Title if metadata.Title != "" { ilst = append(ilst, buildTextAtom("©nam", metadata.Title)...) } - // ©ART - Artist if metadata.Artist != "" { ilst = append(ilst, buildTextAtom("©ART", metadata.Artist)...) } - // ©alb - Album if metadata.Album != "" { ilst = append(ilst, buildTextAtom("©alb", metadata.Album)...) } - // aART - Album Artist if metadata.AlbumArtist != "" { ilst = append(ilst, buildTextAtom("aART", metadata.AlbumArtist)...) } - // ©day - Year/Date if metadata.Date != "" { ilst = append(ilst, buildTextAtom("©day", metadata.Date)...) } - // trkn - Track Number if metadata.TrackNumber > 0 { ilst = append(ilst, buildTrackNumberAtom(metadata.TrackNumber, metadata.TotalTracks)...) } - // disk - Disc Number if metadata.DiscNumber > 0 { ilst = append(ilst, buildDiscNumberAtom(metadata.DiscNumber, 0)...) } - // ©lyr - Lyrics if metadata.Lyrics != "" { ilst = append(ilst, buildTextAtom("©lyr", metadata.Lyrics)...) } - // covr - Cover Art if len(coverData) > 0 { ilst = append(ilst, buildCoverAtom(coverData)...) } - // Build ilst atom ilstSize := 8 + len(ilst) ilstAtom := make([]byte, 4) ilstAtom[0] = byte(ilstSize >> 24) @@ -624,7 +600,6 @@ func buildMetaAtom(metadata Metadata, coverData []byte) []byte { 0, // null terminator } - // Build meta atom metaContent := append([]byte{0, 0, 0, 0}, hdlr...) // version + flags + hdlr metaContent = append(metaContent, ilstAtom...) @@ -644,7 +619,6 @@ func buildMetaAtom(metadata Metadata, coverData []byte) []byte { func buildTextAtom(name, value string) []byte { valueBytes := []byte(value) - // data atom dataSize := 16 + len(valueBytes) dataAtom := make([]byte, 4) dataAtom[0] = byte(dataSize >> 24) @@ -656,7 +630,6 @@ func buildTextAtom(name, value string) []byte { dataAtom = append(dataAtom, 0, 0, 0, 0) // locale dataAtom = append(dataAtom, valueBytes...) - // container atom atomSize := 8 + len(dataAtom) atom := make([]byte, 4) atom[0] = byte(atomSize >> 24) @@ -671,7 +644,6 @@ func buildTextAtom(name, value string) []byte { // buildTrackNumberAtom builds trkn atom func buildTrackNumberAtom(track, total int) []byte { - // data atom with track number dataAtom := []byte{ 0, 0, 0, 24, // size 'd', 'a', 't', 'a', @@ -683,7 +655,6 @@ func buildTrackNumberAtom(track, total int) []byte { 0, 0, // padding } - // trkn atom atomSize := 8 + len(dataAtom) atom := make([]byte, 4) atom[0] = byte(atomSize >> 24) @@ -698,7 +669,6 @@ func buildTrackNumberAtom(track, total int) []byte { // buildDiscNumberAtom builds disk atom func buildDiscNumberAtom(disc, total int) []byte { - // data atom with disc number dataAtom := []byte{ 0, 0, 0, 22, // size 'd', 'a', 't', 'a', @@ -709,7 +679,6 @@ func buildDiscNumberAtom(disc, total int) []byte { byte(total >> 8), byte(total), // total discs } - // disk atom atomSize := 8 + len(dataAtom) atom := make([]byte, 4) atom[0] = byte(atomSize >> 24) @@ -724,13 +693,11 @@ func buildDiscNumberAtom(disc, total int) []byte { // buildCoverAtom builds covr atom with image data func buildCoverAtom(coverData []byte) []byte { - // Detect image type (JPEG = 13, PNG = 14) imageType := byte(13) // default JPEG if len(coverData) > 8 && coverData[0] == 0x89 && coverData[1] == 'P' && coverData[2] == 'N' && coverData[3] == 'G' { imageType = 14 // PNG } - // data atom dataSize := 16 + len(coverData) dataAtom := make([]byte, 4) dataAtom[0] = byte(dataSize >> 24) @@ -742,7 +709,6 @@ func buildCoverAtom(coverData []byte) []byte { dataAtom = append(dataAtom, 0, 0, 0, 0) // locale dataAtom = append(dataAtom, coverData...) - // covr atom atomSize := 8 + len(dataAtom) atom := make([]byte, 4) atom[0] = byte(atomSize >> 24) @@ -762,7 +728,6 @@ func GetM4AQuality(filePath string) (AudioQuality, error) { return AudioQuality{}, fmt.Errorf("failed to read M4A file: %w", err) } - // Find moov -> trak -> mdia -> minf -> stbl -> stsd moovPos := findAtom(data, "moov", 0) if moovPos < 0 { return AudioQuality{}, fmt.Errorf("moov atom not found") diff --git a/go_backend/progress.go b/go_backend/progress.go index aca7d070..722b620d 100644 --- a/go_backend/progress.go +++ b/go_backend/progress.go @@ -49,7 +49,6 @@ func getProgress() DownloadProgress { multiMu.RLock() defer multiMu.RUnlock() - // Find first active item for _, item := range multiProgress.Items { return DownloadProgress{ CurrentFile: item.ItemID, @@ -249,10 +248,7 @@ func (pw *ItemProgressWriter) Write(p []byte) (int, error) { } pw.current += int64(n) - // Update progress when we've received at least 64KB since last update - // Also update on first write to show download has started if pw.lastReported == 0 || pw.current-pw.lastReported >= progressUpdateThreshold { - // Calculate speed (MB/s) based on bytes received since last update now := time.Now() elapsed := now.Sub(pw.lastTime).Seconds() var speedMBps float64 diff --git a/go_backend/qobuz.go b/go_backend/qobuz.go index 350b54f0..6e94ed4c 100644 --- a/go_backend/qobuz.go +++ b/go_backend/qobuz.go @@ -855,7 +855,6 @@ func (q *QobuzDownloader) GetDownloadURL(trackID int64, quality string) (string, return "", fmt.Errorf("no Qobuz API available") } - // Use parallel approach - request from all APIs simultaneously _, downloadURL, err := getQobuzDownloadURLParallel(apis, trackID, quality) if err != nil { return "", err @@ -899,7 +898,6 @@ func (q *QobuzDownloader) DownloadFile(downloadURL, outputPath, itemID string) e } expectedSize := resp.ContentLength - // Set total bytes if available if expectedSize > 0 && itemID != "" { SetItemBytesTotal(itemID, expectedSize) } @@ -909,16 +907,13 @@ func (q *QobuzDownloader) DownloadFile(downloadURL, outputPath, itemID string) e return err } - // Use buffered writer for better performance (256KB buffer) bufWriter := bufio.NewWriterSize(out, 256*1024) - // Use item progress writer with buffered output var written int64 if itemID != "" { progressWriter := NewItemProgressWriter(bufWriter, itemID) written, err = io.Copy(progressWriter, resp.Body) } else { - // Fallback: direct copy without progress tracking written, err = io.Copy(bufWriter, resp.Body) } @@ -926,7 +921,6 @@ func (q *QobuzDownloader) DownloadFile(downloadURL, outputPath, itemID string) e flushErr := bufWriter.Flush() closeErr := out.Close() - // Check for any errors if err != nil { os.Remove(outputPath) if isDownloadCancelled(itemID) { @@ -970,18 +964,15 @@ type QobuzDownloadResult struct { func downloadFromQobuz(req DownloadRequest) (QobuzDownloadResult, error) { downloader := NewQobuzDownloader() - // Check for existing file first if existingFile, exists := checkISRCExistsInternal(req.OutputDir, req.ISRC); exists { return QobuzDownloadResult{FilePath: "EXISTS:" + existingFile}, nil } - // Convert expected duration from ms to seconds expectedDurationSec := req.DurationMS / 1000 var track *QobuzTrack var err error - // STRATEGY 0: Use pre-fetched Qobuz ID from Odesli enrichment (highest priority) if req.QobuzID != "" { GoLog("[Qobuz] Using Qobuz ID from Odesli enrichment: %s\n", req.QobuzID) var trackID int64 @@ -1052,7 +1043,6 @@ func downloadFromQobuz(req DownloadRequest) (QobuzDownloadResult, error) { GetTrackIDCache().SetQobuz(req.ISRC, track.ID) } - // Build filename filename := buildFilenameFromTemplate(req.FilenameFormat, map[string]interface{}{ "title": req.TrackName, "artist": req.ArtistName, @@ -1064,7 +1054,6 @@ func downloadFromQobuz(req DownloadRequest) (QobuzDownloadResult, error) { filename = sanitizeFilename(filename) + ".flac" outputPath := filepath.Join(req.OutputDir, filename) - // Check if file already exists if fileInfo, statErr := os.Stat(outputPath); statErr == nil && fileInfo.Size() > 0 { return QobuzDownloadResult{FilePath: "EXISTS:" + outputPath}, nil } @@ -1083,12 +1072,10 @@ func downloadFromQobuz(req DownloadRequest) (QobuzDownloadResult, error) { } GoLog("[Qobuz] Using quality: %s (mapped from %s)\n", qobuzQuality, req.Quality) - // Get actual quality from track metadata actualBitDepth := track.MaximumBitDepth actualSampleRate := int(track.MaximumSamplingRate * 1000) // Convert kHz to Hz GoLog("[Qobuz] Actual quality: %d-bit/%.1fkHz\n", actualBitDepth, track.MaximumSamplingRate) - // Get download URL using parallel API requests downloadURL, err := downloader.GetDownloadURL(track.ID, qobuzQuality) if err != nil { return QobuzDownloadResult{}, fmt.Errorf("failed to get download URL: %w", err) @@ -1120,16 +1107,11 @@ func downloadFromQobuz(req DownloadRequest) (QobuzDownloadResult, error) { // Wait for parallel operations to complete <-parallelDone - // Set progress to 100% and status to finalizing (before embedding) - // This makes the UI show "Finalizing..." while embedding happens if req.ItemID != "" { SetItemProgress(req.ItemID, 1.0, 0, 0) SetItemFinalizing(req.ItemID) } - // Embed metadata using parallel-fetched cover data - // Use metadata from the actual Qobuz track found (more accurate than request) but prefer - // requested Album Name to avoid ISRC version mismatches (e.g. Compilations vs Original) albumName := track.Album.Title if req.AlbumName != "" { albumName = req.AlbumName @@ -1147,7 +1129,6 @@ func downloadFromQobuz(req DownloadRequest) (QobuzDownloadResult, error) { ISRC: track.ISRC, } - // Use cover data from parallel fetch var coverData []byte if parallelResult != nil && parallelResult.CoverData != nil { coverData = parallelResult.CoverData diff --git a/go_backend/spotify.go b/go_backend/spotify.go index 37846633..cbb1657e 100644 --- a/go_backend/spotify.go +++ b/go_backend/spotify.go @@ -89,7 +89,6 @@ func HasSpotifyCredentials() bool { return true } - // Check environment variables if os.Getenv("SPOTIFY_CLIENT_ID") != "" && os.Getenv("SPOTIFY_CLIENT_SECRET") != "" { return true } @@ -102,12 +101,10 @@ func getCredentials() (string, string, error) { credentialsMu.RLock() defer credentialsMu.RUnlock() - // Check custom credentials first if customClientID != "" && customClientSecret != "" { return customClientID, customClientSecret, nil } - // Check environment variables clientID := os.Getenv("SPOTIFY_CLIENT_ID") clientSecret := os.Getenv("SPOTIFY_CLIENT_SECRET") @@ -393,10 +390,8 @@ func (c *SpotifyMetadataClient) SearchTracks(ctx context.Context, query string, // SearchAll searches for tracks and artists on Spotify func (c *SpotifyMetadataClient) SearchAll(ctx context.Context, query string, trackLimit, artistLimit int) (*SearchAllResult, error) { - // Create cache key cacheKey := fmt.Sprintf("all:%s:%d:%d", query, trackLimit, artistLimit) - // Check cache first c.cacheMu.RLock() if entry, ok := c.searchCache[cacheKey]; ok && !entry.isExpired() { c.cacheMu.RUnlock() @@ -510,7 +505,6 @@ func (c *SpotifyMetadataClient) fetchTrack(ctx context.Context, trackID, token s } func (c *SpotifyMetadataClient) fetchAlbum(ctx context.Context, albumID, token string) (*AlbumResponsePayload, error) { - // Check cache first c.cacheMu.RLock() if entry, ok := c.albumCache[albumID]; ok && !entry.isExpired() { c.cacheMu.RUnlock() @@ -768,7 +762,6 @@ func (c *SpotifyMetadataClient) fetchPlaylist(ctx context.Context, playlistID, t } func (c *SpotifyMetadataClient) fetchArtist(ctx context.Context, artistID, token string) (*ArtistResponsePayload, error) { - // Check cache first c.cacheMu.RLock() if entry, ok := c.artistCache[artistID]; ok && !entry.isExpired() { c.cacheMu.RUnlock() diff --git a/go_backend/tidal.go b/go_backend/tidal.go index abb299e6..91ad16b9 100644 --- a/go_backend/tidal.go +++ b/go_backend/tidal.go @@ -118,7 +118,6 @@ func NewTidalDownloader() *TidalDownloader { clientSecret: string(clientSecret), } - // Get first available API apis := globalTidalDownloader.GetAvailableAPIs() if len(apis) > 0 { globalTidalDownloader.apiURL = apis[0] @@ -1451,7 +1450,6 @@ func isLatinScript(s string) bool { func downloadFromTidal(req DownloadRequest) (TidalDownloadResult, error) { downloader := NewTidalDownloader() - // Check for existing file first if existingFile, exists := checkISRCExistsInternal(req.OutputDir, req.ISRC); exists { return TidalDownloadResult{FilePath: "EXISTS:" + existingFile}, nil } @@ -1519,7 +1517,6 @@ func downloadFromTidal(req DownloadRequest) (TidalDownloadResult, error) { var tidalURL string var slErr error - // Check if SpotifyID is actually a Deezer ID (format: "deezer:xxxxx") if strings.HasPrefix(req.SpotifyID, "deezer:") { deezerID := strings.TrimPrefix(req.SpotifyID, "deezer:") GoLog("[Tidal] Using Deezer ID for SongLink lookup: %s\n", deezerID) @@ -1530,12 +1527,10 @@ func downloadFromTidal(req DownloadRequest) (TidalDownloadResult, error) { } if slErr == nil && tidalURL != "" { - // Extract track ID and get track info trackID, idErr := downloader.GetTrackIDFromURL(tidalURL) if idErr == nil { track, err = downloader.GetTrackInfoByID(trackID) if track != nil { - // Get artist name from track tidalArtist := track.Artist.Name if len(track.Artists) > 0 { var artistNames []string @@ -1545,7 +1540,6 @@ func downloadFromTidal(req DownloadRequest) (TidalDownloadResult, error) { tidalArtist = strings.Join(artistNames, ", ") } - // Verify artist matches (SongLink is already accurate, no title check needed) if !artistsMatch(req.ArtistName, tidalArtist) { GoLog("[Tidal] Artist mismatch from SongLink: expected '%s', got '%s'. Rejecting.\n", req.ArtistName, tidalArtist) @@ -1617,12 +1611,10 @@ func downloadFromTidal(req DownloadRequest) (TidalDownloadResult, error) { } GoLog("[Tidal] Match found: '%s' by '%s' (duration: %ds)\n", track.Title, tidalArtist, track.Duration) - // Cache the track ID for future use if req.ISRC != "" { GetTrackIDCache().SetTidal(req.ISRC, track.ID) } - // Build filename filename := buildFilenameFromTemplate(req.FilenameFormat, map[string]interface{}{ "title": req.TrackName, "artist": req.ArtistName, @@ -1634,7 +1626,6 @@ func downloadFromTidal(req DownloadRequest) (TidalDownloadResult, error) { filename = sanitizeFilename(filename) + ".flac" outputPath := filepath.Join(req.OutputDir, filename) - // Check if file already exists (both FLAC and M4A) if fileInfo, statErr := os.Stat(outputPath); statErr == nil && fileInfo.Size() > 0 { return TidalDownloadResult{FilePath: "EXISTS:" + outputPath}, nil } @@ -1650,14 +1641,12 @@ func downloadFromTidal(req DownloadRequest) (TidalDownloadResult, error) { os.Remove(tmpPath) } - // Determine quality to use (default to LOSSLESS if not specified) quality := req.Quality if quality == "" { quality = "LOSSLESS" } GoLog("[Tidal] Using quality: %s\n", quality) - // Get download URL using parallel API requests downloadInfo, err := downloader.GetDownloadURL(track.ID, quality) if err != nil { return TidalDownloadResult{}, fmt.Errorf("failed to get download URL: %w", err) @@ -1702,18 +1691,13 @@ func downloadFromTidal(req DownloadRequest) (TidalDownloadResult, error) { // Wait for parallel operations to complete <-parallelDone - // Set progress to 100% and status to finalizing (before embedding) - // This makes the UI show "Finalizing..." while embedding happens if req.ItemID != "" { SetItemProgress(req.ItemID, 1.0, 0, 0) SetItemFinalizing(req.ItemID) } - // Check if file was saved as M4A (DASH stream) instead of FLAC - // downloadFromManifest saves DASH streams as .m4a (m4aPath already defined above) actualOutputPath := outputPath if _, err := os.Stat(m4aPath); err == nil { - // File was saved as M4A, use that path actualOutputPath = m4aPath GoLog("[Tidal] File saved as M4A (DASH stream): %s\n", actualOutputPath) } else if _, err := os.Stat(outputPath); err != nil { @@ -1734,7 +1718,6 @@ func downloadFromTidal(req DownloadRequest) (TidalDownloadResult, error) { ISRC: track.ISRC, // Use actual ISRC from Tidal } - // Use cover data from parallel fetch var coverData []byte if parallelResult != nil && parallelResult.CoverData != nil { coverData = parallelResult.CoverData diff --git a/lib/app.dart b/lib/app.dart index 64a4b34c..224072e9 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -9,7 +9,6 @@ import 'package:spotiflac_android/theme/dynamic_color_wrapper.dart'; import 'package:spotiflac_android/l10n/app_localizations.dart'; final _routerProvider = Provider((ref) { - // Only watch isFirstLaunch to prevent router rebuild on other settings changes final isFirstLaunch = ref.watch(settingsProvider.select((s) => s.isFirstLaunch)); return GoRouter( @@ -35,7 +34,6 @@ class SpotiFLACApp extends ConsumerWidget { final router = ref.watch(_routerProvider); final localeString = ref.watch(settingsProvider.select((s) => s.locale)); - // Convert locale string to Locale object Locale? locale; if (localeString != 'system') { locale = Locale(localeString); diff --git a/lib/main.dart b/lib/main.dart index e0ec75e4..615c2750 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -48,7 +48,6 @@ class _EagerInitializationState extends ConsumerState<_EagerInitialization> { final extensionsDir = '${appDir.path}/extensions'; final dataDir = '${appDir.path}/extension_data'; - // Create directories if needed await Directory(extensionsDir).create(recursive: true); await Directory(dataDir).create(recursive: true); diff --git a/lib/providers/download_queue_provider.dart b/lib/providers/download_queue_provider.dart index ab2293a9..6da7e81e 100644 --- a/lib/providers/download_queue_provider.dart +++ b/lib/providers/download_queue_provider.dart @@ -415,11 +415,9 @@ class DownloadQueueNotifier extends Notifier { state = state.copyWith(items: pendingItems); _log.i('Restored ${pendingItems.length} pending items from storage'); - // Auto-resume queue processing Future.microtask(() => _processQueue()); } else { _log.d('No pending items to restore'); - // Clear storage since nothing to restore await prefs.remove(_queueStorageKey); } } else { @@ -603,7 +601,6 @@ class DownloadQueueNotifier extends Notifier { if (state.outputDir.isEmpty) { try { if (Platform.isIOS) { - // iOS: Use Documents directory (accessible via Files app) final dir = await getApplicationDocumentsDirectory(); final musicDir = Directory('${dir.path}/SpotiFLAC'); if (!await musicDir.exists()) { @@ -1347,7 +1344,6 @@ class DownloadQueueNotifier extends Notifier { return; } - // Set currentDownload for UI reference state = state.copyWith(currentDownload: item); updateItemStatus(item.id, DownloadStatus.downloading); @@ -1408,7 +1404,6 @@ class DownloadQueueNotifier extends Notifier { releaseDate: data['release_date'] as String?, deezerId: rawId, availability: trackToDownload.availability, - // Preserve albumType from API response or original track albumType: (data['album_type'] as String?) ?? trackToDownload.albumType, source: trackToDownload.source, ); @@ -1439,7 +1434,6 @@ class DownloadQueueNotifier extends Notifier { albumFolderStructure: settings.albumFolderStructure, ); - // Use quality override if set, otherwise use default from settings final quality = item.qualityOverride ?? state.audioQuality; Map result; @@ -1449,7 +1443,6 @@ class DownloadQueueNotifier extends Notifier { final useExtensions = settings.useExtensionProviders && hasActiveExtensions; if (useExtensions) { - // Use extension providers (includes fallback to built-in services) _log.d('Using extension providers for download'); _log.d( 'Quality: $quality${item.qualityOverride != null ? ' (override)' : ''}', @@ -1528,7 +1521,6 @@ class DownloadQueueNotifier extends Notifier { ); if (currentItem.status == DownloadStatus.skipped) { _log.i('Download was cancelled, skipping result processing'); - // Delete the downloaded file if it exists final filePath = result['file_path'] as String?; if (filePath != null && result['success'] == true) { try { @@ -1614,7 +1606,6 @@ class DownloadQueueNotifier extends Notifier { 'Backend metadata - Track: $backendTrackNum, Disc: $backendDiscNum, Year: $backendYear', ); - // Create updated track object with safety check for 0/null final newTrackNumber = (backendTrackNum != null && backendTrackNum > 0) ? backendTrackNum @@ -1647,7 +1638,6 @@ class DownloadQueueNotifier extends Notifier { ); } - // Use enriched/updated track for metadata embedding await _embedMetadataAndCover(flacPath, finalTrack); _log.d('Metadata and cover embedded successfully'); } catch (e) { @@ -1714,7 +1704,6 @@ class DownloadQueueNotifier extends Notifier { final backendSampleRate = result['actual_sample_rate'] as int?; final backendISRC = result['isrc'] as String?; - // Log cover URL for debugging _log.d('Saving to history - coverUrl: ${trackToDownload.coverUrl}'); final historyAlbumArtist = @@ -1782,7 +1771,6 @@ class DownloadQueueNotifier extends Notifier { return; } - // Convert error type string to enum DownloadErrorType errorType; switch (errorTypeStr) { case 'not_found': diff --git a/lib/providers/extension_provider.dart b/lib/providers/extension_provider.dart index ecba3f30..7086051b 100644 --- a/lib/providers/extension_provider.dart +++ b/lib/providers/extension_provider.dart @@ -175,12 +175,10 @@ class SearchBehavior { /// Get thumbnail size based on configuration /// Returns (width, height) tuple (double, double) getThumbnailSize({double defaultSize = 56}) { - // If custom dimensions specified, use them if (thumbnailWidth != null && thumbnailHeight != null) { return (thumbnailWidth!.toDouble(), thumbnailHeight!.toDouble()); } - // Otherwise use ratio presets switch (thumbnailRatio) { case 'wide': // 16:9 - YouTube style return (defaultSize * 16 / 9, defaultSize); @@ -558,10 +556,8 @@ class ExtensionNotifier extends Notifier { await PlatformBridge.setExtensionEnabled(extensionId, enabled); _log.d('Set extension $extensionId enabled: $enabled'); - // Get extension info before updating state final ext = state.extensions.where((e) => e.id == extensionId).firstOrNull; - // Update local state final extensions = state.extensions.map((e) { if (e.id == extensionId) { return e.copyWith(enabled: enabled); @@ -571,18 +567,15 @@ class ExtensionNotifier extends Notifier { state = state.copyWith(extensions: extensions); - // If disabling an extension, reset related settings if (!enabled && ext != null) { final settings = ref.read(settingsProvider); - // If this extension was the search provider, clear it and reset to Deezer if (settings.searchProvider == extensionId) { ref.read(settingsProvider.notifier).setSearchProvider(null); ref.read(settingsProvider.notifier).setMetadataSource('deezer'); _log.d('Cleared search provider and reset to Deezer because extension $extensionId was disabled'); } - // If this extension was the default download service, reset to Tidal if (ext.hasDownloadProvider && settings.defaultService == extensionId) { ref.read(settingsProvider.notifier).setDefaultService('tidal'); _log.d('Reset default service to Tidal because extension $extensionId was disabled'); diff --git a/lib/screens/album_screen.dart b/lib/screens/album_screen.dart index c58f7b78..ad678432 100644 --- a/lib/screens/album_screen.dart +++ b/lib/screens/album_screen.dart @@ -89,14 +89,12 @@ class _AlbumScreenState extends ConsumerState { try { Map metadata; - // Check if this is a Deezer album ID (format: "deezer:123456") if (widget.albumId.startsWith('deezer:')) { final deezerAlbumId = widget.albumId.replaceFirst('deezer:', ''); // ignore: avoid_print print('[AlbumScreen] Fetching from Deezer: $deezerAlbumId'); metadata = await PlatformBridge.getDeezerMetadata('album', deezerAlbumId); } else { - // Spotify album - use fallback method // ignore: avoid_print print('[AlbumScreen] Fetching from Spotify with fallback: ${widget.albumId}'); final url = 'https://open.spotify.com/album/${widget.albumId}'; @@ -448,7 +446,6 @@ class _AlbumTrackItem extends ConsumerWidget { return state.items.where((item) => item.track.id == track.id).firstOrNull; })); - // Check if track is in history (already downloaded before) final isInHistory = ref.watch(downloadHistoryProvider.select((state) { return state.isDownloaded(track.id); })); diff --git a/lib/screens/artist_screen.dart b/lib/screens/artist_screen.dart index d16b5008..3d224a8e 100644 --- a/lib/screens/artist_screen.dart +++ b/lib/screens/artist_screen.dart @@ -112,7 +112,6 @@ class _ArtistScreenState extends ConsumerState { ); }); - // If this is an extension artist, use provided data only - don't fetch from Spotify/Deezer if (widget.extensionId != null) { _albums = widget.albums; _topTracks = widget.topTracks; @@ -122,8 +121,6 @@ class _ArtistScreenState extends ConsumerState { return; } - // Priority: widget data > cache > fetch - // But always fetch if topTracks is missing (to get popular tracks) final cached = _ArtistCache.get(widget.artistId); if (widget.albums != null) { @@ -132,7 +129,6 @@ class _ArtistScreenState extends ConsumerState { _headerImageUrl = widget.headerImageUrl; _monthlyListeners = widget.monthlyListeners; - // If we have albums but no top tracks, fetch to get them if (_topTracks == null || _topTracks!.isEmpty) { _fetchDiscography(); } @@ -159,14 +155,12 @@ class _ArtistScreenState extends ConsumerState { String? headerImage; int? listeners; - // Check if this is a Deezer artist ID (format: "deezer:123456") if (widget.artistId.startsWith('deezer:')) { final deezerArtistId = widget.artistId.replaceFirst('deezer:', ''); final metadata = await PlatformBridge.getDeezerMetadata('artist', deezerArtistId); final albumsList = metadata['albums'] as List; albums = albumsList.map((a) => _parseArtistAlbum(a as Map)).toList(); } else { - // Spotify artist - use extension handler via URL final url = 'https://open.spotify.com/artist/${widget.artistId}'; final result = await PlatformBridge.handleURLWithExtension(url); @@ -302,8 +296,6 @@ class _ArtistScreenState extends ConsumerState { /// Build Spotify-style header with full-width image and artist name overlay Widget _buildHeader(BuildContext context, ColorScheme colorScheme) { - // Use header image if available, otherwise fall back to cover URL - // Prefer: fetched header > widget header > widget cover String? imageUrl = _headerImageUrl; if (imageUrl == null || imageUrl.isEmpty) { imageUrl = widget.headerImageUrl; @@ -467,7 +459,6 @@ class _ArtistScreenState extends ConsumerState { return state.items.where((item) => item.track.id == track.id).firstOrNull; })); - // Check if track is in history (already downloaded before) final isInHistory = ref.watch(downloadHistoryProvider.select((state) { return state.isDownloaded(track.id); })); diff --git a/lib/screens/downloaded_album_screen.dart b/lib/screens/downloaded_album_screen.dart index a95d8820..2f51391a 100644 --- a/lib/screens/downloaded_album_screen.dart +++ b/lib/screens/downloaded_album_screen.dart @@ -159,7 +159,6 @@ class _DownloadedAlbumScreenState extends ConsumerState { final colorScheme = Theme.of(context).colorScheme; final bottomPadding = MediaQuery.of(context).padding.bottom; - // Watch history and get tracks for this album (reactive!) final allHistoryItems = ref.watch(downloadHistoryProvider.select((s) => s.items)); final tracks = _getAlbumTracks(allHistoryItems); diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 32c0cae7..3e37bde9 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -329,7 +329,6 @@ class _HomeScreenState extends ConsumerState { } Future _openCollection(Track track) async { - // Get the extension ID from the track source final extensionId = track.source; if (extensionId == null) return; diff --git a/lib/screens/home_tab.dart b/lib/screens/home_tab.dart index 960224a5..baa6be79 100644 --- a/lib/screens/home_tab.dart +++ b/lib/screens/home_tab.dart @@ -76,10 +76,8 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient } void _onSearchChanged() { final text = _urlController.text.trim(); - // Update search text state for MainShell back button handling ref.read(trackProvider.notifier).setSearchText(text.isNotEmpty); - // Update typing state immediately for UI transition if (text.isNotEmpty && !_isTyping) { setState(() => _isTyping = true); } else if (text.isEmpty && _isTyping) { @@ -103,17 +101,13 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient if (_lastSearchQuery == searchKey) return; _lastSearchQuery = searchKey; - // Check if extension search provider is set AND still enabled final isExtensionEnabled = searchProvider != null && searchProvider.isNotEmpty && extState.extensions.any((e) => e.id == searchProvider && e.enabled); if (isExtensionEnabled) { - // Use custom search from extension await ref.read(trackProvider.notifier).customSearch(searchProvider, query); } else { - // Use default search (Deezer/Spotify) - // Also clear searchProvider if it was set but extension is disabled if (searchProvider != null && searchProvider.isNotEmpty && !isExtensionEnabled) { ref.read(settingsProvider.notifier).setSearchProvider(null); } @@ -238,7 +232,6 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient int currentProgress = 0; int totalTracks = 0; - // Use StatefulBuilder to update dialog content bool dialogShown = false; StateSetter? setDialogState; @@ -322,8 +315,6 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient action: SnackBarAction( label: l10n.snackbarViewQueue, onPressed: () { - // Navigate to queue tab (handled by main_shell index) - // We don't have direct access to set index here easily without provider }, ), ), @@ -348,14 +339,12 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient } }); - // Use select() to only rebuild when specific fields change final tracks = ref.watch(trackProvider.select((s) => s.tracks)); final searchArtists = ref.watch(trackProvider.select((s) => s.searchArtists)); final isLoading = ref.watch(trackProvider.select((s) => s.isLoading)); final error = ref.watch(trackProvider.select((s) => s.error)); final hasSearchedBefore = ref.watch(settingsProvider.select((s) => s.hasSearchedBefore)); - // Watch extension state to update search hint when extensions load/change ref.watch(extensionProvider.select((s) => s.isInitialized)); ref.watch(extensionProvider.select((s) => s.extensions)); @@ -612,7 +601,6 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient // Merge with recent downloads to make the list more populated final historyItems = ref.read(downloadHistoryProvider).items; - // Convert download history to RecentAccessItem format final downloadItems = historyItems.take(10).where((h) => h.spotifyId != null && h.spotifyId!.isNotEmpty).map((h) => RecentAccessItem( id: h.spotifyId!, name: h.trackName, @@ -748,7 +736,6 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient ], ), ), - // Delete button (like Spotify's X) IconButton( icon: Icon(Icons.close, size: 20, color: colorScheme.onSurfaceVariant), onPressed: () { @@ -767,7 +754,6 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient switch (item.type) { case RecentAccessType.artist: - // Check if artist is from extension (not spotify/deezer) if (item.providerId != null && item.providerId!.isNotEmpty && item.providerId != 'deezer' && item.providerId != 'spotify') { Navigator.push(context, MaterialPageRoute( builder: (context) => ExtensionArtistScreen( @@ -1389,16 +1375,13 @@ class _TrackItemWithStatus extends ConsumerWidget { return state.items.where((item) => item.track.id == track.id).firstOrNull; })); - // Check if track is in history (already downloaded before) final isInHistory = ref.watch(downloadHistoryProvider.select((state) { return state.isDownloaded(track.id); })); - // Get thumbnail size from extension if track is from extension double thumbWidth = 56; double thumbHeight = 56; - // Get extension ID from track.source or from TrackState.searchExtensionId final trackState = ref.watch(trackProvider); final extensionId = track.source ?? trackState.searchExtensionId; @@ -1499,7 +1482,6 @@ class _TrackItemWithStatus extends ConsumerWidget { // If already in queue, do nothing if (isQueued) return; - // If in history, check if file still exists if (isInHistory) { final historyItem = ref.read(downloadHistoryProvider.notifier).getBySpotifyId(track.id); if (historyItem != null) { diff --git a/lib/screens/main_shell.dart b/lib/screens/main_shell.dart index 65a72da3..189e350b 100644 --- a/lib/screens/main_shell.dart +++ b/lib/screens/main_shell.dart @@ -36,7 +36,6 @@ class _MainShellState extends ConsumerState { void initState() { super.initState(); _pageController = PageController(initialPage: _currentIndex); - // Check for updates after first frame WidgetsBinding.instance.addPostFrameCallback((_) { _checkForUpdates(); _setupShareListener(); @@ -44,7 +43,6 @@ class _MainShellState extends ConsumerState { } void _setupShareListener() { - // Check for pending URL that was received before listener was ready final pendingUrl = ShareIntentService().consumePendingUrl(); if (pendingUrl != null) { _log.d('Processing pending shared URL: $pendingUrl'); @@ -124,8 +122,6 @@ class _MainShellState extends ConsumerState { void _onPageChanged(int index) { if (_currentIndex != index) { setState(() => _currentIndex = index); - // Unfocus any text field when switching tabs to prevent keyboard from appearing - // Use primaryFocus for more aggressive unfocus that works with keep-alive widgets FocusManager.instance.primaryFocus?.unfocus(); } } @@ -134,7 +130,6 @@ class _MainShellState extends ConsumerState { void _handleBackPress() { final trackState = ref.read(trackProvider); - // Check if keyboard is visible - if so, just dismiss keyboard, don't clear search final isKeyboardVisible = MediaQuery.of(context).viewInsets.bottom > 0; if (isKeyboardVisible) { FocusManager.instance.primaryFocus?.unfocus(); @@ -144,7 +139,6 @@ class _MainShellState extends ConsumerState { // If on Home tab and showing recent access mode, exit it if (_currentIndex == 0 && trackState.isShowingRecentAccess) { ref.read(trackProvider.notifier).setShowingRecentAccess(false); - // Also unfocus search bar when exiting recent access mode FocusManager.instance.primaryFocus?.unfocus(); return; } @@ -189,7 +183,6 @@ class _MainShellState extends ConsumerState { final showStore = ref.watch(settingsProvider.select((s) => s.showExtensionStore)); final storeUpdatesCount = ref.watch(storeProvider.select((s) => s.updatesAvailableCount)); - // Check if keyboard is visible (bottom inset > 0 means keyboard is showing) final isKeyboardVisible = MediaQuery.of(context).viewInsets.bottom > 0; // Determine if we can pop (for predictive back animation) @@ -202,7 +195,6 @@ class _MainShellState extends ConsumerState { !trackState.isShowingRecentAccess && !isKeyboardVisible; - // Build tabs and destinations based on settings final tabs = [ const HomeTab(), QueueTab( diff --git a/lib/screens/playlist_screen.dart b/lib/screens/playlist_screen.dart index 9f4a3d95..448ef642 100644 --- a/lib/screens/playlist_screen.dart +++ b/lib/screens/playlist_screen.dart @@ -222,7 +222,6 @@ class _PlaylistTrackItem extends ConsumerWidget { return state.items.where((item) => item.track.id == track.id).firstOrNull; })); - // Check if track is in history (already downloaded before) final isInHistory = ref.watch(downloadHistoryProvider.select((state) { return state.isDownloaded(track.id); })); diff --git a/lib/screens/settings/download_settings_page.dart b/lib/screens/settings/download_settings_page.dart index 0e81b508..ec41382f 100644 --- a/lib/screens/settings/download_settings_page.dart +++ b/lib/screens/settings/download_settings_page.dart @@ -473,7 +473,6 @@ class DownloadSettingsPage extends ConsumerWidget { // iOS: Show options dialog _showIOSDirectoryOptions(context, ref); } else { - // Android: Use file picker final result = await FilePicker.platform.getDirectoryPath(); if (result != null) { ref.read(settingsProvider.notifier).setDownloadDirectory(result); diff --git a/lib/screens/settings/extensions_page.dart b/lib/screens/settings/extensions_page.dart index 2cce5c74..5fd8bc7d 100644 --- a/lib/screens/settings/extensions_page.dart +++ b/lib/screens/settings/extensions_page.dart @@ -500,7 +500,6 @@ class _MetadataPriorityItem extends ConsumerWidget { final extState = ref.watch(extensionProvider); final colorScheme = Theme.of(context).colorScheme; - // Check if any extension has metadata provider final hasMetadataExtensions = extState.extensions .any((e) => e.enabled && e.hasMetadataProvider); diff --git a/lib/screens/settings/options_settings_page.dart b/lib/screens/settings/options_settings_page.dart index 39dff0f1..acc4ed3a 100644 --- a/lib/screens/settings/options_settings_page.dart +++ b/lib/screens/settings/options_settings_page.dart @@ -838,7 +838,6 @@ class _MetadataSourceSelector extends ConsumerWidget { // Not selected if extension is active isSelected: currentSource == 'deezer' && !hasExtensionSearch, onTap: () { - // If extension was active, reset it to default if (hasExtensionSearch) { ref.read(settingsProvider.notifier).setSearchProvider(null); } diff --git a/lib/screens/settings/settings_tab.dart b/lib/screens/settings/settings_tab.dart index c9d1899a..5bf00423 100644 --- a/lib/screens/settings/settings_tab.dart +++ b/lib/screens/settings/settings_tab.dart @@ -123,16 +123,12 @@ class SettingsTab extends ConsumerWidget { } void _navigateTo(BuildContext context, Widget page) { - // Unfocus any focused widget before navigating to prevent keyboard from appearing on return FocusManager.instance.primaryFocus?.unfocus(); Navigator.of(context).push( - // Use PageRouteBuilder for better predictive back gesture support - // MaterialPageRoute can cause freeze on some devices with gesture navigation PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => page, transitionsBuilder: (context, animation, secondaryAnimation, child) { - // Use slide transition similar to MaterialPageRoute const begin = Offset(1.0, 0.0); const end = Offset.zero; const curve = Curves.easeInOut; diff --git a/lib/theme/dynamic_color_wrapper.dart b/lib/theme/dynamic_color_wrapper.dart index b90569b0..88b84983 100644 --- a/lib/theme/dynamic_color_wrapper.dart +++ b/lib/theme/dynamic_color_wrapper.dart @@ -45,7 +45,6 @@ class DynamicColorWrapper extends ConsumerWidget { darkScheme = _applyAmoledColors(darkScheme); } - // Build themes final lightTheme = AppTheme.light(dynamicScheme: lightScheme); final darkTheme = AppTheme.dark(dynamicScheme: darkScheme, isAmoled: themeSettings.useAmoled); diff --git a/lib/utils/logger.dart b/lib/utils/logger.dart index e3119030..c2627d1a 100644 --- a/lib/utils/logger.dart +++ b/lib/utils/logger.dart @@ -55,7 +55,6 @@ class LogBuffer extends ChangeNotifier { static bool get loggingEnabled => _loggingEnabled; static set loggingEnabled(bool value) { _loggingEnabled = value; - // Also notify Go backend about logging state if (value) { PlatformBridge.setGoLoggingEnabled(true).catchError((_) {}); } else { @@ -121,7 +120,6 @@ class LogBuffer extends ChangeNotifier { ); } } catch (_) { - // Use current time if parsing fails } } @@ -146,7 +144,6 @@ class LogBuffer extends ChangeNotifier { void clear() { _entries.clear(); _lastGoLogIndex = 0; - // Also clear Go backend logs PlatformBridge.clearGoLogs().catchError((_) {}); notifyListeners(); } @@ -249,8 +246,6 @@ class AppLogger { late final Logger? _logger; AppLogger(this._tag) { - // Only create Logger instance in debug mode - // In release mode, we write directly to LogBuffer if (kDebugMode) { _logger = Logger( printer: SimplePrinter(printTime: false, colors: false), @@ -276,7 +271,6 @@ class AppLogger { if (kDebugMode) { _logger?.d(message); } else { - // In release mode, write directly to buffer _addToBuffer('DEBUG', message); } }