package banksync import ( "context" "errors" "io" "sync" "time" ) // ErrBusy is returned by Runner.Run when another run is still in progress. var ErrBusy = errors.New("a sync is already running") // RunStatus describes one finished run of a Runner. type RunStatus struct { Started, Finished time.Time Trigger string // "manual" or "scheduled" Err error } // Runner serialises bank sync runs so a scheduled run and a manual one never // overlap (overlapping runs could append the same rows twice), and remembers // the outcome of the last run for display. type Runner struct { job func(ctx context.Context, out io.Writer) error running sync.Mutex mu sync.Mutex last RunStatus hasLast bool } // NewRunner wraps job, which performs one full sync and writes a // human-readable log to out. func NewRunner(job func(ctx context.Context, out io.Writer) error) *Runner { return &Runner{job: job} } // Run executes the job unless one is already running, in which case it // returns ErrBusy without waiting. func (r *Runner) Run(ctx context.Context, trigger string, out io.Writer) error { if !r.running.TryLock() { return ErrBusy } defer r.running.Unlock() st := RunStatus{Started: time.Now(), Trigger: trigger} st.Err = r.job(ctx, out) st.Finished = time.Now() r.mu.Lock() r.last, r.hasLast = st, true r.mu.Unlock() return st.Err } // Last returns the outcome of the most recent finished run, if any. func (r *Runner) Last() (RunStatus, bool) { r.mu.Lock() defer r.mu.Unlock() return r.last, r.hasLast }