mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-03-31 09:01:33 +02:00
- Fix M4A/ALAC scan silently failing on Samsung by adding proper fallback to scanFromFilename when ReadM4ATags fails (consistent with MP3/FLAC/Ogg) - Propagate displayNameHint to all format scanners so fd numbers (214, 207) no longer appear as track names when /proc/self/fd/ paths are used - Cache /proc/self/fd/ readability in Kotlin to skip failed attempts after first failure, reducing error log noise and improving scan speed on Samsung - Fix Qobuz download returning wrong album cover when track exists on multiple albums by preferring req.CoverURL over API default - Fix FFmpeg M4A metadata save failing with 'codec not currently supported in container' by forcing mp4 muxer instead of ipod when cover art present - Clean up FLAC SAF temp file after metadata write-back (was leaking) - Update LRC lyrics tag to credit Paxsenix API - Remove log message truncation, defer to UI preview truncation instead
203 lines
5.2 KiB
Go
203 lines
5.2 KiB
Go
package gobackend
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type LogEntry struct {
|
|
Timestamp string `json:"timestamp"`
|
|
Level string `json:"level"`
|
|
Tag string `json:"tag"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
type LogBuffer struct {
|
|
entries []LogEntry
|
|
maxSize int
|
|
mu sync.RWMutex
|
|
loggingEnabled bool
|
|
}
|
|
|
|
const (
|
|
defaultLogBufferSize = 500
|
|
)
|
|
|
|
var (
|
|
globalLogBuffer *LogBuffer
|
|
logBufferOnce sync.Once
|
|
|
|
authorizationBearerPattern = regexp.MustCompile(`(?i)\bAuthorization\b\s*[:=]\s*Bearer\s+[A-Za-z0-9._~+/\-]+=*`)
|
|
genericKeyValuePattern = regexp.MustCompile(`(?i)\b(access[_\s-]?token|refresh[_\s-]?token|id[_\s-]?token|client[_\s-]?secret|authorization|password|api[_\s-]?key)\b(\s*[:=]\s*)([^\s,;]+)`)
|
|
queryTokenPattern = regexp.MustCompile(`(?i)([?&](?:access_token|refresh_token|id_token|token|client_secret|api_key|apikey|password)=)[^&\s]+`)
|
|
bearerTokenPattern = regexp.MustCompile(`(?i)\bBearer\s+[A-Za-z0-9._~+/\-]+=*`)
|
|
)
|
|
|
|
func sanitizeSensitiveLogText(message string) string {
|
|
redacted := message
|
|
redacted = authorizationBearerPattern.ReplaceAllString(redacted, "Authorization: Bearer [REDACTED]")
|
|
redacted = genericKeyValuePattern.ReplaceAllString(redacted, `${1}${2}[REDACTED]`)
|
|
redacted = queryTokenPattern.ReplaceAllString(redacted, `${1}[REDACTED]`)
|
|
redacted = bearerTokenPattern.ReplaceAllString(redacted, "Bearer [REDACTED]")
|
|
return redacted
|
|
}
|
|
|
|
func GetLogBuffer() *LogBuffer {
|
|
logBufferOnce.Do(func() {
|
|
globalLogBuffer = &LogBuffer{
|
|
entries: make([]LogEntry, 0, defaultLogBufferSize),
|
|
maxSize: defaultLogBufferSize,
|
|
loggingEnabled: false, // Default: disabled for performance (user can enable in settings)
|
|
}
|
|
})
|
|
return globalLogBuffer
|
|
}
|
|
|
|
func (lb *LogBuffer) SetLoggingEnabled(enabled bool) {
|
|
lb.mu.Lock()
|
|
defer lb.mu.Unlock()
|
|
lb.loggingEnabled = enabled
|
|
}
|
|
|
|
func (lb *LogBuffer) IsLoggingEnabled() bool {
|
|
lb.mu.RLock()
|
|
defer lb.mu.RUnlock()
|
|
return lb.loggingEnabled
|
|
}
|
|
|
|
func (lb *LogBuffer) Add(level, tag, message string) {
|
|
lb.mu.Lock()
|
|
defer lb.mu.Unlock()
|
|
|
|
if !lb.loggingEnabled && level != "ERROR" && level != "FATAL" {
|
|
return
|
|
}
|
|
|
|
message = sanitizeSensitiveLogText(message)
|
|
|
|
entry := LogEntry{
|
|
Timestamp: time.Now().Format("15:04:05.000"),
|
|
Level: level,
|
|
Tag: tag,
|
|
Message: message,
|
|
}
|
|
|
|
if len(lb.entries) >= lb.maxSize {
|
|
lb.entries = lb.entries[1:]
|
|
}
|
|
lb.entries = append(lb.entries, entry)
|
|
|
|
fmt.Printf("[%s] %s\n", tag, message)
|
|
}
|
|
|
|
func (lb *LogBuffer) GetAll() string {
|
|
lb.mu.RLock()
|
|
defer lb.mu.RUnlock()
|
|
|
|
jsonBytes, _ := json.Marshal(lb.entries)
|
|
return string(jsonBytes)
|
|
}
|
|
|
|
func (lb *LogBuffer) getSince(index int) ([]LogEntry, int) {
|
|
lb.mu.RLock()
|
|
defer lb.mu.RUnlock()
|
|
|
|
if index < 0 {
|
|
index = 0
|
|
}
|
|
if index >= len(lb.entries) {
|
|
return []LogEntry{}, len(lb.entries)
|
|
}
|
|
|
|
entries := lb.entries[index:]
|
|
return entries, len(lb.entries)
|
|
}
|
|
|
|
func (lb *LogBuffer) Clear() {
|
|
lb.mu.Lock()
|
|
defer lb.mu.Unlock()
|
|
lb.entries = lb.entries[:0]
|
|
}
|
|
|
|
func (lb *LogBuffer) Count() int {
|
|
lb.mu.RLock()
|
|
defer lb.mu.RUnlock()
|
|
return len(lb.entries)
|
|
}
|
|
|
|
func LogDebug(tag, format string, args ...interface{}) {
|
|
GetLogBuffer().Add("DEBUG", tag, fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
func LogInfo(tag, format string, args ...interface{}) {
|
|
GetLogBuffer().Add("INFO", tag, fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
func LogWarn(tag, format string, args ...interface{}) {
|
|
GetLogBuffer().Add("WARN", tag, fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
func LogError(tag, format string, args ...interface{}) {
|
|
GetLogBuffer().Add("ERROR", tag, fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
// GoLog is a drop-in replacement for fmt.Printf that also logs to buffer
|
|
// It parses the tag from the format string if it starts with [Tag]
|
|
func GoLog(format string, args ...interface{}) {
|
|
message := fmt.Sprintf(format, args...)
|
|
message = strings.TrimSuffix(message, "\n")
|
|
|
|
// Extract tag from message if present (e.g., "[Tidal] message")
|
|
tag := "Go"
|
|
level := "INFO"
|
|
|
|
if strings.HasPrefix(message, "[") {
|
|
endBracket := strings.Index(message, "]")
|
|
if endBracket > 1 {
|
|
tag = message[1:endBracket]
|
|
message = strings.TrimSpace(message[endBracket+1:])
|
|
}
|
|
}
|
|
|
|
// Determine level from message content
|
|
msgLower := strings.ToLower(message)
|
|
if strings.Contains(msgLower, "error") || strings.Contains(msgLower, "failed") {
|
|
level = "ERROR"
|
|
} else if strings.Contains(msgLower, "warning") || strings.Contains(msgLower, "warn") {
|
|
level = "WARN"
|
|
} else if strings.Contains(msgLower, "success") || strings.Contains(msgLower, "match found") {
|
|
level = "INFO"
|
|
} else if strings.Contains(msgLower, "searching") || strings.Contains(msgLower, "trying") || strings.Contains(msgLower, "found") {
|
|
level = "DEBUG"
|
|
}
|
|
|
|
GetLogBuffer().Add(level, tag, message)
|
|
}
|
|
|
|
func GetLogs() string {
|
|
return GetLogBuffer().GetAll()
|
|
}
|
|
|
|
func GetLogsSince(index int) string {
|
|
entries, nextIndex := GetLogBuffer().getSince(index)
|
|
logsJson, _ := json.Marshal(entries)
|
|
result := fmt.Sprintf(`{"logs":%s,"next_index":%d}`, string(logsJson), nextIndex)
|
|
return result
|
|
}
|
|
|
|
func ClearLogs() {
|
|
GetLogBuffer().Clear()
|
|
}
|
|
|
|
func GetLogCount() int {
|
|
return GetLogBuffer().Count()
|
|
}
|
|
|
|
func SetLoggingEnabled(enabled bool) {
|
|
GetLogBuffer().SetLoggingEnabled(enabled)
|
|
}
|