0.2.76 - Icon Packs

This commit is contained in:
ponzischeme89
2026-08-18 14:59:29 +12:00
parent 36d171e51b
commit 8c847c59b8
70 changed files with 5267 additions and 673 deletions
+61 -23
View File
@@ -250,6 +250,14 @@ func (s *Syncer) run(
return result, nil
}
// EmbyCredentials is how the ingest worker borrows the same account the scheduled import
// uses. One rule for "who does the gateway talk to Emby as" rather than two, so a
// household with a service account configured never has an event-driven read appear in
// somebody's Emby history as their television.
func (s *Syncer) EmbyCredentials(ctx context.Context) (emby.Credentials, error) {
return s.credentials(ctx)
}
// credentials prefers the configured service account and otherwise borrows the most
// recent TV session.
func (s *Syncer) credentials(ctx context.Context) (emby.Credentials, error) {
@@ -333,44 +341,74 @@ func (s *Syncer) Find(ctx context.Context, term string, limit int) ([]json.RawMe
return found, nil
}
// disabledSyncPoll is how often a switched-off schedule wakes to ask whether it still is.
// A setting an operator has just changed must not need a restart, which is the same reason
// the Emby reachability probe keeps ticking slowly while it is off.
const disabledSyncPoll = 5 * time.Minute
// Schedule runs an incremental import on an interval until ctx is cancelled.
//
// New episodes tend to land through the day and films weekly; an hourly incremental pass
// covers both without ever asking Emby for the whole catalogue again.
func (s *Syncer) Schedule(ctx context.Context, interval time.Duration, paused ...func() bool) {
if interval <= 0 {
// It is reconciliation now rather than discovery. Where the *arr webhooks are configured, a
// file is in the catalogue within a minute of Sonarr or Radarr putting it there and this
// pass exists for what they do not manage — media dropped in by hand, a title edited in
// Emby, a webhook that never arrived because the gateway was down. Where they are not, it
// is still the only thing that notices anything, which is why the interval is the
// operator's rather than a constant.
//
// interval is a function rather than a value because it is read every cycle: an operator
// who has just lengthened the sweep must see that take effect without restarting the
// container. Zero means switched off, and this keeps waking to ask.
func (s *Syncer) Schedule(
ctx context.Context, interval func() time.Duration, paused ...func() bool,
) {
if interval == nil {
s.log.Info("library auto-sync disabled")
return
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
s.log.Info("library auto-sync scheduled", "interval", interval.String())
s.log.Info("library auto-sync scheduled", "interval", durationLabel(interval()))
for {
wait := interval()
disabled := wait <= 0
if disabled {
wait = disabledSyncPoll
}
timer := time.NewTimer(wait)
select {
case <-ctx.Done():
timer.Stop()
return
case <-ticker.C:
if len(paused) > 0 && paused[0] != nil && paused[0]() {
s.log.Debug("skipping scheduled sync; quiet time is active")
case <-timer.C:
}
timer.Stop()
if disabled {
continue
}
if len(paused) > 0 && paused[0] != nil && paused[0]() {
s.log.Debug("skipping scheduled sync; quiet time is active")
continue
}
if s.Running() {
s.log.Info("skipping scheduled sync; one is already running")
continue
}
if _, err := s.Sync(ctx, "incremental", "schedule"); err != nil {
if errors.Is(err, ErrNoCredentials) {
// Nobody has signed in yet. Not worth an error-level log every hour.
s.log.Info("skipping scheduled sync; no credentials yet")
continue
}
if s.Running() {
s.log.Info("skipping scheduled sync; one is already running")
continue
}
if _, err := s.Sync(ctx, "incremental", "schedule"); err != nil {
if errors.Is(err, ErrNoCredentials) {
// Nobody has signed in yet. Not worth an error-level log every hour.
s.log.Info("skipping scheduled sync; no credentials yet")
continue
}
s.log.Error("scheduled sync failed", "error", err)
}
s.log.Error("scheduled sync failed", "error", err)
}
}
}
func durationLabel(value time.Duration) string {
if value <= 0 {
return "off"
}
return value.String()
}
// syncItem mirrors the Emby fields promoted to columns.
type syncItem struct {
ID string `json:"Id"`