<commit_before>package cover

import (
	"context"
	"errors"
	"path"
	"sync"
)

// Cover represents cover api.
type Cover interface {
	Rescan(context.Context, map[string][]string) error
	GetURLs(map[string][]string) ([]string, bool)
}

var (
	// ErrAlreadyShutdown returns if already Shutdown is called
	ErrAlreadyShutdown = errors.New("cover: already shutdown")
	// ErrAlreadyUpdating returns if already Update is called
	ErrAlreadyUpdating = errors.New("cover: update already started")
)

// Batch provides background updater for cover api.
type Batch struct {
	covers []Cover
	sem    chan struct{}
	e      chan bool

	shutdownMu sync.Mutex
	shutdownCh chan struct{}
	shutdownB  bool
}

// NewBatch creates Batch from some cover api.
func NewBatch(covers []Cover) *Batch {
	ret := &Batch{
		covers:     covers,
		sem:        make(chan struct{}, 1),
		e:          make(chan bool, 1),
		shutdownCh: make(chan struct{}),
	}
	ret.sem <- struct{}{}
	return ret
}

// Event returns event chan which returns bool updating or not.
func (b *Batch) Event() <-chan bool {
	return b.e
}

// GetURLs returns images url list.
func (b *Batch) GetURLs(song map[string][]string) (urls []string, updated bool) {
	allUpdated := true
	for _, cover := range b.covers {
		urls, updated = cover.GetURLs(song)
		if len(urls) != 0 {
			return
		}
		if !updated {
			allUpdated = false
		}
	}
	return urls, allUpdated
}

// Update updates image url database.
func (b *Batch) Update(songs []map[string][]string) error {
	select {
	case _, ok := <-b.sem:
		if !ok {
			return ErrAlreadyShutdown
		}
	default:
		return ErrAlreadyUpdating
	}
	go func() {
		select {
		case b.e <- true:
		default:
		}
		defer func() { b.sem <- struct{}{} }()
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()
		go func() {
			select {
			case <-ctx.Done():
			case <-b.shutdownCh:
				cancel()
			}
		}()
		targets := make(map[string]map[string][]string, len(songs))
		for _, song := range songs {
			if len(song["file"]) == 1 {
				targets[path.Dir(song["file"][0])] = song
			}
		}
		for _, song := range targets {
			for _, c := range b.covers {
				c.Rescan(ctx, song)
				urls, _ := c.GetURLs(song)
				if len(urls) > 0 {
					break
				}
			}
		}
		select {
		case <-ctx.Done():
		case b.e <- false:
		default:
		}
	}()
	return nil
}

// Shutdown  gracefully shuts down cover updater.
func (b *Batch) Shutdown(ctx context.Context) error {<commit_msg>add coverart error log<commit_after>package cover

import (
	"context"
	"errors"
	"log"
	"path"
	"sync"

	"github.com/meiraka/vv/internal/songs"
)

// Cover represents cover api.
type Cover interface {
	Rescan(context.Context, map[string][]string) error
	GetURLs(map[string][]string) ([]string, bool)
}

var (
	// ErrAlreadyShutdown returns if already Shutdown is called
	ErrAlreadyShutdown = errors.New("cover: already shutdown")
	// ErrAlreadyUpdating returns if already Update is called
	ErrAlreadyUpdating = errors.New("cover: update already started")
)

// Batch provides background updater for cover api.
type Batch struct {
	covers []Cover
	sem    chan struct{}
	e      chan bool

	shutdownMu sync.Mutex
	shutdownCh chan struct{}
	shutdownB  bool
}

// NewBatch creates Batch from some cover api.
func NewBatch(covers []Cover) *Batch {
	ret := &Batch{
		covers:     covers,
		sem:        make(chan struct{}, 1),
		e:          make(chan bool, 1),
		shutdownCh: make(chan struct{}),
	}
	ret.sem <- struct{}{}
	return ret
}

// Event returns event chan which returns bool updating or not.
func (b *Batch) Event() <-chan bool {
	return b.e
}

// GetURLs returns images url list.
func (b *Batch) GetURLs(song map[string][]string) (urls []string, updated bool) {
	allUpdated := true
	for _, cover := range b.covers {
		urls, updated = cover.GetURLs(song)
		if len(urls) != 0 {
			return
		}
		if !updated {
			allUpdated = false
		}
	}
	return urls, allUpdated
}

var songsTag = songs.Tag

// Update updates image url database.
func (b *Batch) Update(songs []map[string][]string) error {
	select {
	case _, ok := <-b.sem:
		if !ok {
			return ErrAlreadyShutdown
		}
	default:
		return ErrAlreadyUpdating
	}
	go func() {
		select {
		case b.e <- true:
		default:
		}
		defer func() { b.sem <- struct{}{} }()
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()
		go func() {
			select {
			case <-ctx.Done():
			case <-b.shutdownCh:
				cancel()
			}
		}()
		targets := make(map[string]map[string][]string, len(songs))
		for _, song := range songs {
			if len(song["file"]) == 1 {
				targets[path.Dir(song["file"][0])] = song
			}
		}
		for _, song := range targets {
			for _, c := range b.covers {
				if err := c.Rescan(ctx, song); err != nil {
					log.Printf("cover: %v: %v", songsTag(song, "file"), err)
					continue
				}
				urls, _ := c.GetURLs(song)
				if len(urls) > 0 {
					break
				}
			}
		}
		select {
		case <-ctx.Done():
		case b.e <- false:
		default:
		}
	}()
	return nil
}

// Shutdown  gracefully shuts down cover updater.
func (b *Batch) Shutdown(ctx context.Context) error {
{'fraction_non_alphanumeric': 0.1419252816762206, 'fraction_numerical': 0.0023720102787112077, 'mean_word_length': 3.154351395730706, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '899930', 'n_tokens_mistral': 1989, 'n_tokens_neox': 1772, 'n_words': 511}