diff --git a/scanner/controller.go b/scanner/controller.go index 36048e6f3..0b3e5d122 100644 --- a/scanner/controller.go +++ b/scanner/controller.go @@ -98,6 +98,7 @@ type ProgressInfo struct { ChangesDetected bool Warning string Error string + ForceUpdate bool } type scanner interface { @@ -292,7 +293,7 @@ func (s *controller) trackProgress(ctx context.Context, progress <-chan *Progres ScanType: scanType, ElapsedTime: elapsed, } - if s.limiter != nil { + if s.limiter != nil && !p.ForceUpdate { s.limiter.Do(func() { s.sendMessage(ctx, status) }) } else { s.sendMessage(ctx, status) diff --git a/scanner/scanner.go b/scanner/scanner.go index 21de5f956..f08dec311 100644 --- a/scanner/scanner.go +++ b/scanner/scanner.go @@ -127,6 +127,7 @@ func (s *scannerImpl) scanAll(ctx context.Context, fullScan bool, progress chan< func (s *scannerImpl) runGC(ctx context.Context, state *scanState) func() error { return func() error { + state.sendProgress(&ProgressInfo{ForceUpdate: true}) return s.ds.WithTx(func(tx model.DataStore) error { if state.changesDetected.Load() { start := time.Now() diff --git a/ui/src/layout/ActivityPanel.jsx b/ui/src/layout/ActivityPanel.jsx index f7371bc2f..806c49d88 100644 --- a/ui/src/layout/ActivityPanel.jsx +++ b/ui/src/layout/ActivityPanel.jsx @@ -21,6 +21,7 @@ import { GiMagnifyingGlass } from 'react-icons/gi' import subsonic from '../subsonic' import { scanStatusUpdate } from '../actions' import { useInterval } from '../common' +import { useScanElapsedTime } from './useScanElapsedTime' import { formatDuration, formatShortDuration } from '../utils' import config from '../config' @@ -70,6 +71,10 @@ const ActivityPanel = () => { const serverStart = useSelector((state) => state.activity.serverStart) const up = serverStart.startTime const scanStatus = useSelector((state) => state.activity.scanStatus) + const elapsed = useScanElapsedTime( + scanStatus.scanning, + scanStatus.elapsedTime, + ) const classes = useStyles({ up: up && !scanStatus.error }) const translate = useTranslate() const notify = useNotify() @@ -178,7 +183,7 @@ const ActivityPanel = () => { {translate('activity.elapsedTime')}: - {formatShortDuration(scanStatus.elapsedTime)} + {formatShortDuration(elapsed)} diff --git a/ui/src/layout/useScanElapsedTime.jsx b/ui/src/layout/useScanElapsedTime.jsx new file mode 100644 index 000000000..1e04f02b0 --- /dev/null +++ b/ui/src/layout/useScanElapsedTime.jsx @@ -0,0 +1,14 @@ +import { useEffect, useState } from 'react' +import { useInterval } from '../common' + +export const useScanElapsedTime = (scanning, elapsedTime) => { + const [elapsed, setElapsed] = useState(Number(elapsedTime) || 0) + + useEffect(() => { + setElapsed(Number(elapsedTime) || 0) + }, [elapsedTime]) + + useInterval(() => setElapsed((prev) => prev + 1e9), scanning ? 1000 : null) + + return elapsed +} diff --git a/ui/src/layout/useScanElapsedTime.test.jsx b/ui/src/layout/useScanElapsedTime.test.jsx new file mode 100644 index 000000000..efe958f90 --- /dev/null +++ b/ui/src/layout/useScanElapsedTime.test.jsx @@ -0,0 +1,50 @@ +import { renderHook, act } from '@testing-library/react-hooks' +import { vi } from 'vitest' +import { useScanElapsedTime } from './useScanElapsedTime' + +describe('useScanElapsedTime', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.useRealTimers() + }) + + it('increments elapsed time while scanning', () => { + const { result } = renderHook( + ({ scanning, elapsed }) => useScanElapsedTime(scanning, elapsed), + { + initialProps: { scanning: true, elapsed: 0 }, + }, + ) + + act(() => { + vi.advanceTimersByTime(3000) + }) + + expect(result.current).toBe(3e9) + }) + + it('stops incrementing when not scanning', () => { + const { result, rerender } = renderHook( + ({ scanning, elapsed }) => useScanElapsedTime(scanning, elapsed), + { + initialProps: { scanning: false, elapsed: 2e9 }, + }, + ) + + act(() => { + vi.advanceTimersByTime(2000) + }) + + expect(result.current).toBe(2e9) + + rerender({ scanning: true, elapsed: 2e9 }) + act(() => { + vi.advanceTimersByTime(1000) + }) + + expect(result.current).toBe(3e9) + }) +})