Files
SpotiFLAC-Mobile/go_backend/cancel.go
T

183 lines
3.9 KiB
Go

package gobackend
import (
"context"
"errors"
"sync"
)
// ErrDownloadCancelled is returned when a download is cancelled by the user.
var ErrDownloadCancelled = errors.New("download cancelled")
// ErrExtensionRequestCancelled is returned when a UI-driven extension request
// is superseded by a newer home/search request.
var ErrExtensionRequestCancelled = errors.New("extension request cancelled")
type cancelEntry struct {
ctx context.Context
cancel context.CancelFunc
canceled bool
refs int
}
type cancelRegistry struct {
mu sync.Mutex
entries map[string]*cancelEntry
}
var (
downloadCancels = &cancelRegistry{entries: make(map[string]*cancelEntry)}
extensionRequestCancels = &cancelRegistry{entries: make(map[string]*cancelEntry)}
)
func (r *cancelRegistry) init(id string) context.Context {
if id == "" {
return context.Background()
}
r.mu.Lock()
defer r.mu.Unlock()
if entry, ok := r.entries[id]; ok {
if entry.ctx == nil {
ctx, cancel := context.WithCancel(context.Background())
entry.ctx = ctx
entry.cancel = cancel
if entry.canceled && entry.cancel != nil {
entry.cancel()
}
}
entry.refs++
return entry.ctx
}
ctx, cancel := context.WithCancel(context.Background())
r.entries[id] = &cancelEntry{
ctx: ctx,
cancel: cancel,
canceled: false,
refs: 1,
}
return ctx
}
func (r *cancelRegistry) context(id string) context.Context {
if id == "" {
return context.Background()
}
r.mu.Lock()
defer r.mu.Unlock()
if entry, ok := r.entries[id]; ok && entry.ctx != nil {
return entry.ctx
}
return context.Background()
}
func (r *cancelRegistry) requestCancel(id string) {
if id == "" {
return
}
r.mu.Lock()
if entry, ok := r.entries[id]; ok {
entry.canceled = true
if entry.cancel != nil {
entry.cancel()
}
} else {
r.entries[id] = &cancelEntry{canceled: true}
}
r.mu.Unlock()
}
func (r *cancelRegistry) isCancelled(id string) bool {
if id == "" {
return false
}
r.mu.Lock()
entry, ok := r.entries[id]
canceled := ok && entry.canceled
r.mu.Unlock()
return canceled
}
// resetIfIdle removes a cancellation entry that has no active work attached
// (refs <= 0). Such entries exist to catch an item that is just about to
// start, but if the item never starts the flag lingers and the next explicit
// retry would consume it and abort immediately.
func (r *cancelRegistry) resetIfIdle(id string) {
if id == "" {
return
}
r.mu.Lock()
if entry, ok := r.entries[id]; ok && entry.refs <= 0 {
delete(r.entries, id)
}
r.mu.Unlock()
}
func (r *cancelRegistry) release(id string) {
if id == "" {
return
}
r.mu.Lock()
if entry, ok := r.entries[id]; ok {
entry.refs--
if entry.refs <= 0 {
delete(r.entries, id)
}
}
r.mu.Unlock()
}
func initDownloadCancel(itemID string) context.Context {
return downloadCancels.init(itemID)
}
func downloadCancelContext(itemID string) context.Context {
return downloadCancels.context(itemID)
}
func cancelDownload(itemID string) {
if itemID == "" {
return
}
downloadCancels.requestCancel(itemID)
RemoveItemProgress(itemID)
}
func isDownloadCancelled(itemID string) bool {
return downloadCancels.isCancelled(itemID)
}
func resetDownloadCancel(itemID string) {
downloadCancels.resetIfIdle(itemID)
}
func clearDownloadCancel(itemID string) {
downloadCancels.release(itemID)
}
func initExtensionRequestCancel(requestID string) context.Context {
return extensionRequestCancels.init(requestID)
}
func extensionRequestCancelContext(requestID string) context.Context {
return extensionRequestCancels.context(requestID)
}
func cancelExtensionRequest(requestID string) {
extensionRequestCancels.requestCancel(requestID)
}
func isExtensionRequestCancelled(requestID string) bool {
return extensionRequestCancels.isCancelled(requestID)
}
func clearExtensionRequestCancel(requestID string) {
extensionRequestCancels.release(requestID)
}