mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-03-31 09:01:33 +02:00
- Reduce polling interval from 800ms to 1200ms across download progress, library scan, and Android native stream - Add dirty-flag caching to Go GetMultiProgress() to skip redundant JSON marshaling - Replace eager provider initialization with staggered Timer-based warmup (400/900/1600ms) - Add snapshot-based incremental library scan to avoid large MethodChannel payloads - Move history stats and grouped album filtering to Riverpod providers for better cache invalidation - Cap home tab history preview to 48 items with deep equality wrapper to reduce rebuilds - Throttle foreground service notification updates to 2% progress buckets - Migrate PageView to PageView.builder with AutomaticKeepAliveClientMixin - Add comparison table to README
264 lines
5.8 KiB
Go
264 lines
5.8 KiB
Go
package gobackend
|
|
|
|
import (
|
|
"encoding/json"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
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"`
|
|
}
|
|
|
|
type ItemProgress struct {
|
|
ItemID string `json:"item_id"`
|
|
BytesTotal int64 `json:"bytes_total"`
|
|
BytesReceived int64 `json:"bytes_received"`
|
|
Progress float64 `json:"progress"`
|
|
SpeedMBps float64 `json:"speed_mbps"`
|
|
IsDownloading bool `json:"is_downloading"`
|
|
Status string `json:"status"`
|
|
}
|
|
|
|
type MultiProgress struct {
|
|
Items map[string]*ItemProgress `json:"items"`
|
|
}
|
|
|
|
var (
|
|
downloadDir string
|
|
downloadDirMu sync.RWMutex
|
|
|
|
multiProgress = MultiProgress{Items: make(map[string]*ItemProgress)}
|
|
multiMu sync.RWMutex
|
|
multiProgressDirty = true
|
|
cachedMultiProgress = "{\"items\":{}}"
|
|
)
|
|
|
|
func markMultiProgressDirtyLocked() {
|
|
multiProgressDirty = true
|
|
}
|
|
|
|
func getProgress() DownloadProgress {
|
|
multiMu.RLock()
|
|
defer multiMu.RUnlock()
|
|
|
|
for _, item := range multiProgress.Items {
|
|
return DownloadProgress{
|
|
CurrentFile: item.ItemID,
|
|
Progress: item.Progress * 100,
|
|
BytesTotal: item.BytesTotal,
|
|
BytesReceived: item.BytesReceived,
|
|
IsDownloading: item.IsDownloading,
|
|
Status: item.Status,
|
|
}
|
|
}
|
|
|
|
return DownloadProgress{}
|
|
}
|
|
|
|
func GetMultiProgress() string {
|
|
multiMu.RLock()
|
|
if !multiProgressDirty {
|
|
cached := cachedMultiProgress
|
|
multiMu.RUnlock()
|
|
return cached
|
|
}
|
|
multiMu.RUnlock()
|
|
|
|
multiMu.Lock()
|
|
defer multiMu.Unlock()
|
|
if !multiProgressDirty {
|
|
return cachedMultiProgress
|
|
}
|
|
jsonBytes, err := json.Marshal(multiProgress)
|
|
if err != nil {
|
|
return "{\"items\":{}}"
|
|
}
|
|
cachedMultiProgress = string(jsonBytes)
|
|
multiProgressDirty = false
|
|
return cachedMultiProgress
|
|
}
|
|
|
|
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 "{}"
|
|
}
|
|
|
|
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",
|
|
}
|
|
markMultiProgressDirtyLocked()
|
|
}
|
|
|
|
func SetItemBytesTotal(itemID string, total int64) {
|
|
multiMu.Lock()
|
|
defer multiMu.Unlock()
|
|
|
|
if item, ok := multiProgress.Items[itemID]; ok {
|
|
item.BytesTotal = total
|
|
markMultiProgressDirtyLocked()
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
markMultiProgressDirtyLocked()
|
|
}
|
|
}
|
|
|
|
func SetItemBytesReceivedWithSpeed(itemID string, received int64, speedMBps float64) {
|
|
multiMu.Lock()
|
|
defer multiMu.Unlock()
|
|
|
|
if item, ok := multiProgress.Items[itemID]; ok {
|
|
item.BytesReceived = received
|
|
item.SpeedMBps = speedMBps
|
|
if item.BytesTotal > 0 {
|
|
item.Progress = float64(received) / float64(item.BytesTotal)
|
|
}
|
|
markMultiProgressDirtyLocked()
|
|
}
|
|
}
|
|
|
|
func CompleteItemProgress(itemID string) {
|
|
multiMu.Lock()
|
|
defer multiMu.Unlock()
|
|
|
|
if item, ok := multiProgress.Items[itemID]; ok {
|
|
item.Progress = 1.0
|
|
item.IsDownloading = false
|
|
item.Status = "completed"
|
|
markMultiProgressDirtyLocked()
|
|
}
|
|
}
|
|
|
|
func SetItemProgress(itemID string, progress float64, bytesReceived, bytesTotal int64) {
|
|
multiMu.Lock()
|
|
defer multiMu.Unlock()
|
|
|
|
if item, ok := multiProgress.Items[itemID]; ok {
|
|
item.Progress = progress
|
|
if bytesReceived > 0 {
|
|
item.BytesReceived = bytesReceived
|
|
}
|
|
if bytesTotal > 0 {
|
|
item.BytesTotal = bytesTotal
|
|
}
|
|
markMultiProgressDirtyLocked()
|
|
}
|
|
}
|
|
|
|
func SetItemFinalizing(itemID string) {
|
|
multiMu.Lock()
|
|
defer multiMu.Unlock()
|
|
|
|
if item, ok := multiProgress.Items[itemID]; ok {
|
|
item.Progress = 1.0
|
|
item.Status = "finalizing"
|
|
markMultiProgressDirtyLocked()
|
|
}
|
|
}
|
|
|
|
func RemoveItemProgress(itemID string) {
|
|
multiMu.Lock()
|
|
defer multiMu.Unlock()
|
|
|
|
delete(multiProgress.Items, itemID)
|
|
markMultiProgressDirtyLocked()
|
|
}
|
|
|
|
func ClearAllItemProgress() {
|
|
multiMu.Lock()
|
|
defer multiMu.Unlock()
|
|
|
|
multiProgress.Items = make(map[string]*ItemProgress)
|
|
markMultiProgressDirtyLocked()
|
|
}
|
|
|
|
func setDownloadDir(path string) error {
|
|
downloadDirMu.Lock()
|
|
defer downloadDirMu.Unlock()
|
|
downloadDir = path
|
|
return nil
|
|
}
|
|
|
|
type ItemProgressWriter struct {
|
|
writer interface{ Write([]byte) (int, error) }
|
|
itemID string
|
|
current int64
|
|
lastReported int64
|
|
startTime time.Time
|
|
lastTime time.Time
|
|
lastBytes int64
|
|
}
|
|
|
|
const progressUpdateThreshold = 64 * 1024
|
|
|
|
func NewItemProgressWriter(w interface{ Write([]byte) (int, error) }, itemID string) *ItemProgressWriter {
|
|
now := time.Now()
|
|
return &ItemProgressWriter{
|
|
writer: w,
|
|
itemID: itemID,
|
|
current: 0,
|
|
lastReported: 0,
|
|
startTime: now,
|
|
lastTime: now,
|
|
lastBytes: 0,
|
|
}
|
|
}
|
|
|
|
func (pw *ItemProgressWriter) Write(p []byte) (int, error) {
|
|
if pw.itemID != "" && isDownloadCancelled(pw.itemID) {
|
|
return 0, ErrDownloadCancelled
|
|
}
|
|
n, err := pw.writer.Write(p)
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
pw.current += int64(n)
|
|
|
|
if pw.lastReported == 0 || pw.current-pw.lastReported >= progressUpdateThreshold {
|
|
now := time.Now()
|
|
elapsed := now.Sub(pw.lastTime).Seconds()
|
|
var speedMBps float64
|
|
if elapsed > 0 {
|
|
bytesInInterval := pw.current - pw.lastBytes
|
|
speedMBps = float64(bytesInInterval) / (1024 * 1024) / elapsed
|
|
}
|
|
|
|
SetItemBytesReceivedWithSpeed(pw.itemID, pw.current, speedMBps)
|
|
pw.lastReported = pw.current
|
|
pw.lastTime = now
|
|
pw.lastBytes = pw.current
|
|
}
|
|
return n, nil
|
|
}
|