Files
SpotiFLAC-Mobile/go_backend/progress.go
zarzet b87de1f00a feat: quality picker with track info, update dialog redesign, finalizing notification fix
- Quality picker now shows track name, artist, and cover
- Tap to expand long track titles (icon only shows when truncated)
- Ripple effect follows rounded corners including drag handle
- Update dialog redesigned with Material Expressive 3 style
- Fixed update notification stuck at 100% after download complete
- Ask before download now enabled by default
- Finalizing notification for multi-progress polling
2026-01-03 04:26:19 +07:00

318 lines
8.0 KiB
Go

package gobackend
import (
"encoding/json"
"sync"
)
// DownloadProgress represents current download progress (legacy single download)
type DownloadProgress struct {
CurrentFile string `json:"current_file"`
Progress float64 `json:"progress"`
Speed float64 `json:"speed_mbps"`
BytesTotal int64 `json:"bytes_total"`
BytesReceived int64 `json:"bytes_received"`
IsDownloading bool `json:"is_downloading"`
Status string `json:"status"` // "downloading", "finalizing", "completed"
}
// ItemProgress represents progress for a single download item
type ItemProgress struct {
ItemID string `json:"item_id"`
BytesTotal int64 `json:"bytes_total"`
BytesReceived int64 `json:"bytes_received"`
Progress float64 `json:"progress"` // 0.0 to 1.0
IsDownloading bool `json:"is_downloading"`
Status string `json:"status"` // "downloading", "finalizing", "completed"
}
// MultiProgress holds progress for multiple concurrent downloads
type MultiProgress struct {
Items map[string]*ItemProgress `json:"items"`
}
var (
currentProgress DownloadProgress
progressMu sync.RWMutex
downloadDir string
downloadDirMu sync.RWMutex
// Multi-download progress tracking
multiProgress = MultiProgress{Items: make(map[string]*ItemProgress)}
multiMu sync.RWMutex
)
// getProgress returns current download progress (legacy)
func getProgress() DownloadProgress {
progressMu.RLock()
defer progressMu.RUnlock()
return currentProgress
}
// GetMultiProgress returns progress for all active downloads as JSON
func GetMultiProgress() string {
multiMu.RLock()
defer multiMu.RUnlock()
jsonBytes, err := json.Marshal(multiProgress)
if err != nil {
return "{\"items\":{}}"
}
return string(jsonBytes)
}
// GetItemProgress returns progress for a specific item as JSON
func GetItemProgress(itemID string) string {
multiMu.RLock()
defer multiMu.RUnlock()
if item, ok := multiProgress.Items[itemID]; ok {
jsonBytes, _ := json.Marshal(item)
return string(jsonBytes)
}
return "{}"
}
// StartItemProgress initializes progress tracking for an item
func StartItemProgress(itemID string) {
multiMu.Lock()
defer multiMu.Unlock()
multiProgress.Items[itemID] = &ItemProgress{
ItemID: itemID,
BytesTotal: 0,
BytesReceived: 0,
Progress: 0,
IsDownloading: true,
Status: "downloading",
}
}
// SetItemBytesTotal sets total bytes for an item
func SetItemBytesTotal(itemID string, total int64) {
multiMu.Lock()
defer multiMu.Unlock()
if item, ok := multiProgress.Items[itemID]; ok {
item.BytesTotal = total
}
}
// SetItemBytesReceived sets bytes received for an item
func SetItemBytesReceived(itemID string, received int64) {
multiMu.Lock()
defer multiMu.Unlock()
if item, ok := multiProgress.Items[itemID]; ok {
item.BytesReceived = received
if item.BytesTotal > 0 {
item.Progress = float64(received) / float64(item.BytesTotal)
}
}
}
// CompleteItemProgress marks an item as complete
func CompleteItemProgress(itemID string) {
multiMu.Lock()
defer multiMu.Unlock()
if item, ok := multiProgress.Items[itemID]; ok {
item.Progress = 1.0
item.IsDownloading = false
}
}
// SetItemProgress sets progress for an item directly (used to force 100% before embedding)
func SetItemProgress(itemID string, progress float64, bytesReceived, bytesTotal int64) {
multiMu.Lock()
if item, ok := multiProgress.Items[itemID]; ok {
item.Progress = progress
if bytesReceived > 0 {
item.BytesReceived = bytesReceived
}
if bytesTotal > 0 {
item.BytesTotal = bytesTotal
}
}
multiMu.Unlock()
// Also update legacy progress for backward compatibility
progressMu.Lock()
if progress >= 1.0 {
currentProgress.Progress = 100.0
} else {
currentProgress.Progress = progress * 100.0
}
progressMu.Unlock()
}
// SetItemFinalizing marks an item as finalizing (embedding metadata)
func SetItemFinalizing(itemID string) {
multiMu.Lock()
if item, ok := multiProgress.Items[itemID]; ok {
item.Progress = 1.0
item.Status = "finalizing"
}
multiMu.Unlock()
// Also update legacy progress
progressMu.Lock()
currentProgress.Progress = 100.0
currentProgress.Status = "finalizing"
progressMu.Unlock()
}
// RemoveItemProgress removes progress tracking for an item
func RemoveItemProgress(itemID string) {
multiMu.Lock()
defer multiMu.Unlock()
delete(multiProgress.Items, itemID)
}
// ClearAllItemProgress clears all item progress
func ClearAllItemProgress() {
multiMu.Lock()
defer multiMu.Unlock()
multiProgress.Items = make(map[string]*ItemProgress)
}
// Legacy functions for backward compatibility
// SetDownloadProgress sets the current download progress (MB downloaded)
func SetDownloadProgress(mbDownloaded float64) {
progressMu.Lock()
defer progressMu.Unlock()
currentProgress.Progress = mbDownloaded
currentProgress.IsDownloading = true
}
// SetDownloadSpeed sets the current download speed
func SetDownloadSpeed(speedMBps float64) {
progressMu.Lock()
defer progressMu.Unlock()
currentProgress.Speed = speedMBps
}
// SetCurrentFile sets the current file being downloaded and resets progress
func SetCurrentFile(filename string) {
progressMu.Lock()
defer progressMu.Unlock()
currentProgress.BytesReceived = 0
currentProgress.BytesTotal = 0
currentProgress.Progress = 0
currentProgress.CurrentFile = filename
currentProgress.IsDownloading = true
currentProgress.Status = "downloading"
}
// ResetProgress resets the download progress
func ResetProgress() {
progressMu.Lock()
defer progressMu.Unlock()
currentProgress = DownloadProgress{}
}
// setDownloadDir sets the default download directory
func setDownloadDir(path string) error {
downloadDirMu.Lock()
defer downloadDirMu.Unlock()
downloadDir = path
return nil
}
// getDownloadDir returns the default download directory
func getDownloadDir() string {
downloadDirMu.RLock()
defer downloadDirMu.RUnlock()
return downloadDir
}
// SetDownloading sets the download status
func SetDownloading(status bool) {
progressMu.Lock()
defer progressMu.Unlock()
currentProgress.IsDownloading = status
}
// SetBytesTotal sets total bytes to download
func SetBytesTotal(total int64) {
progressMu.Lock()
defer progressMu.Unlock()
currentProgress.BytesTotal = total
}
// SetBytesReceived sets bytes received so far
func SetBytesReceived(received int64) {
progressMu.Lock()
defer progressMu.Unlock()
currentProgress.BytesReceived = received
if currentProgress.BytesTotal > 0 {
currentProgress.Progress = float64(received) / float64(currentProgress.BytesTotal) * 100
}
}
// ProgressWriter wraps io.Writer to track download progress (legacy single)
type ProgressWriter struct {
writer interface{ Write([]byte) (int, error) }
total int64
current int64
}
// NewProgressWriter creates a new progress writer wrapping an io.Writer
func NewProgressWriter(w interface{ Write([]byte) (int, error) }) *ProgressWriter {
SetBytesReceived(0)
return &ProgressWriter{
writer: w,
current: 0,
total: 0,
}
}
// Write implements io.Writer
func (pw *ProgressWriter) Write(p []byte) (int, error) {
n, err := pw.writer.Write(p)
if err != nil {
return n, err
}
pw.current += int64(n)
pw.total += int64(n)
SetBytesReceived(pw.current)
return n, nil
}
// GetTotal returns total bytes written
func (pw *ProgressWriter) GetTotal() int64 {
return pw.total
}
// ItemProgressWriter wraps io.Writer to track download progress for a specific item
type ItemProgressWriter struct {
writer interface{ Write([]byte) (int, error) }
itemID string
current int64
}
// NewItemProgressWriter creates a new progress writer for a specific item
func NewItemProgressWriter(w interface{ Write([]byte) (int, error) }, itemID string) *ItemProgressWriter {
return &ItemProgressWriter{
writer: w,
itemID: itemID,
current: 0,
}
}
// Write implements io.Writer
func (pw *ItemProgressWriter) Write(p []byte) (int, error) {
n, err := pw.writer.Write(p)
if err != nil {
return n, err
}
pw.current += int64(n)
SetItemBytesReceived(pw.itemID, pw.current)
// Also update legacy progress for backward compatibility
SetBytesReceived(pw.current)
return n, nil
}