mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-03-31 17:10:29 +02:00
- Add multi-select support to library_tracks_folder_screen (wishlist, loved, playlist) with long-press to enter selection mode, animated bottom bar with batch remove/download/add-to-playlist actions, and PopScope exit handling - Create batch showAddTracksToPlaylistSheet in playlist_picker_sheet with playlist thumbnail widget and cover image support - Add playlist grid selection tint overlay in queue_tab - Optimize collection lookups with pre-built _allPlaylistTrackKeys index and isTrackInAnyPlaylist/hasPlaylistTracks accessors - Eagerly initialize localLibraryProvider and libraryCollectionsProvider - Enable SQLite WAL mode and PRAGMA synchronous=NORMAL across all databases - Go backend: duplicate SAF output FDs before provider attempts to prevent fdsan abort on fallback retries; close detached FDs after download completes - Go backend: rewrite compatibilityTransport to try HTTPS first and only fallback to HTTP on transport-level failures, preventing redirect loops - Go backend: enforce HTTPS-only for extension sandbox HTTP clients
89 lines
2.4 KiB
Go
89 lines
2.4 KiB
Go
package gobackend
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
func isFDOutput(outputFD int) bool {
|
|
return outputFD > 0
|
|
}
|
|
|
|
func openOutputForWrite(outputPath string, outputFD int) (*os.File, error) {
|
|
if isFDOutput(outputFD) {
|
|
// Never hand the original detached FD directly to a provider attempt.
|
|
// Fallback chains may retry with another provider after a failure.
|
|
// If the first attempt closes the original FD, its numeric ID can be
|
|
// reused by unrelated resources and a later close may trigger fdsan abort.
|
|
dupFD, err := dupOutputFD(outputFD)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to duplicate output fd %d: %w", outputFD, err)
|
|
}
|
|
if err := prepareDupFDForWrite(dupFD, outputFD); err != nil {
|
|
_ = closeFD(dupFD)
|
|
return nil, err
|
|
}
|
|
return os.NewFile(uintptr(dupFD), fmt.Sprintf("saf_fd_%d_dup_%d", outputFD, dupFD)), nil
|
|
}
|
|
|
|
path := strings.TrimSpace(outputPath)
|
|
if strings.HasPrefix(path, "/proc/self/fd/") {
|
|
// Re-open procfs fd path instead of taking ownership of raw detached fd.
|
|
// Some SAF providers reject O_TRUNC on these descriptors with EACCES/EPERM.
|
|
file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0)
|
|
if err == nil {
|
|
return file, nil
|
|
}
|
|
if strings.Contains(strings.ToLower(err.Error()), "permission denied") {
|
|
return os.OpenFile(path, os.O_WRONLY, 0)
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
return os.Create(outputPath)
|
|
}
|
|
|
|
func prepareDupFDForWrite(dupFD, originalFD int) error {
|
|
// Best-effort reset so retries start writing from byte 0.
|
|
if err := truncateFD(dupFD); err != nil {
|
|
if isBestEffortTruncateError(err) {
|
|
GoLog("[OutputFD] truncate not supported on fd %d (dup of %d): %v\n", dupFD, originalFD, err)
|
|
} else {
|
|
return fmt.Errorf("failed to truncate output fd %d (dup of %d): %w", dupFD, originalFD, err)
|
|
}
|
|
}
|
|
if err := seekFDStart(dupFD); err != nil {
|
|
GoLog("[OutputFD] seek reset failed on fd %d (dup of %d): %v\n", dupFD, originalFD, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func closeOwnedOutputFD(outputFD int) {
|
|
if !isFDOutput(outputFD) {
|
|
return
|
|
}
|
|
|
|
if err := closeFD(outputFD); err != nil {
|
|
if !isBadFD(err) {
|
|
GoLog("[OutputFD] failed to close detached fd %d: %v\n", outputFD, err)
|
|
}
|
|
return
|
|
}
|
|
|
|
GoLog("[OutputFD] closed detached fd %d\n", outputFD)
|
|
}
|
|
|
|
func cleanupOutputOnError(outputPath string, outputFD int) {
|
|
if isFDOutput(outputFD) {
|
|
return
|
|
}
|
|
|
|
path := strings.TrimSpace(outputPath)
|
|
if path == "" || strings.HasPrefix(path, "/proc/self/fd/") {
|
|
return
|
|
}
|
|
|
|
_ = os.Remove(path)
|
|
}
|