mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
downloads: surface daily-limit stall to users
Add daily_limit_reached to the DownloadManager summary (/api/status): True when due (new, non-recurring) downloads exist and every one of them is blocked by the global or per-domain daily download limit. Show a "Max Daily Downloads Reached" banner on the Downloads page when set (and the manager is neither disabled nor stopped). Previously a queue stalled by the daily limit looked broken: the UI reported 0 pending with no explanation, and the skip reason was only visible at DEBUG log level. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
2baa9cbd2e
commit
a4eeba14ee
4 changed files with 84 additions and 0 deletions
|
|
@ -611,6 +611,17 @@ export function DownloadWindowMessage() {
|
|||
return null;
|
||||
}
|
||||
|
||||
export function DailyLimitMessage() {
|
||||
const {status} = React.useContext(StatusContext);
|
||||
const downloads = status ? status.downloads : null;
|
||||
|
||||
if (downloads && downloads.daily_limit_reached && !downloads.disabled && !downloads.stopped) {
|
||||
return <Message icon='pause circle outline' header='Max Daily Downloads Reached'
|
||||
content='Downloads are paused because the daily download limit has been reached. Downloads will resume tomorrow.'/>
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function CookiesUnlockModal({open, onClose, onSuccess}) {
|
||||
const [password, setPassword] = useState('');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
APIButton,
|
||||
CookiesLockedMessage,
|
||||
DisableDownloadsToggle,
|
||||
DailyLimitMessage,
|
||||
DownloadWindowMessage,
|
||||
ErrorMessage,
|
||||
formatFrequency,
|
||||
|
|
@ -684,6 +685,7 @@ export function DownloadsPage() {
|
|||
return <>
|
||||
<WROLModeMessage content='Downloads are disabled because WROL Mode is enabled.'/>
|
||||
<DownloadWindowMessage/>
|
||||
<DailyLimitMessage/>
|
||||
<DisableDownloadsToggle/>
|
||||
<CookiesLockedMessage/>
|
||||
|
||||
|
|
|
|||
|
|
@ -1543,15 +1543,41 @@ class DownloadManager:
|
|||
func.count(Download.id).filter(Download.status == DownloadStatus.pending),
|
||||
func.count(Download.id).filter(Download.frequency != None), # noqa
|
||||
).one()
|
||||
daily_limit_reached = self.daily_limit_reached(session)
|
||||
summary = dict(
|
||||
pending=pending_downloads,
|
||||
recurring=recurring_downloads,
|
||||
disabled=self.is_disabled,
|
||||
stopped=self.is_stopped,
|
||||
outside_download_window=self.outside_download_window,
|
||||
daily_limit_reached=daily_limit_reached,
|
||||
)
|
||||
return summary
|
||||
|
||||
def daily_limit_reached(self, session: Session) -> bool:
|
||||
"""True when due (`new`, non-recurring) downloads exist and every one of them is blocked by the
|
||||
daily download limits. This is what the UI uses to explain a stalled-looking queue; partial
|
||||
blockage (some domains capped, others still downloading) does not count."""
|
||||
limit_per_domain = get_wrolpi_config().download_daily_limit_per_domain or 0
|
||||
limit_global = get_wrolpi_config().download_daily_limit_global or 0
|
||||
if not limit_per_domain and not limit_global:
|
||||
return False
|
||||
|
||||
due_downloads = session.query(Download).filter(
|
||||
Download.status == DownloadStatus.new,
|
||||
Download.frequency.is_(None),
|
||||
).all()
|
||||
if not due_downloads:
|
||||
return False
|
||||
|
||||
global_count, domain_counts = self.daily_download_counts(session)
|
||||
if limit_global and global_count >= limit_global:
|
||||
return True
|
||||
if limit_per_domain:
|
||||
return all(domain_counts.get(self._normalize_download_domain(download), 0) >= limit_per_domain
|
||||
for download in due_downloads)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def calculate_next_download(session: Session, download: Download) -> Optional[datetime]:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1859,6 +1859,51 @@ async def test_daily_limit_counts_failed_attempts(test_session, test_download_ma
|
|||
assert 'https://example.com/new' not in _dispatched_urls(dispatch)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_summary_daily_limit_reached(test_session, test_download_manager, test_downloader):
|
||||
"""get_summary reports daily_limit_reached only when every due download is blocked by a daily limit."""
|
||||
config = get_wrolpi_config()
|
||||
name = test_downloader.name
|
||||
|
||||
# No limits configured.
|
||||
assert test_download_manager.get_summary()['daily_limit_reached'] is False
|
||||
|
||||
config.download_daily_limit_per_domain = 2
|
||||
# No due downloads => not reached, even though limits are configured.
|
||||
_make_processed_download(test_session, name, 'https://example.com/1')
|
||||
_make_processed_download(test_session, name, 'https://example.com/2')
|
||||
test_session.commit()
|
||||
assert test_download_manager.get_summary()['daily_limit_reached'] is False
|
||||
|
||||
# A due download for the capped domain => reached.
|
||||
blocked = Download(url='https://example.com/new', downloader=name, status='new')
|
||||
test_session.add(blocked)
|
||||
test_session.commit()
|
||||
assert test_download_manager.get_summary()['daily_limit_reached'] is True
|
||||
|
||||
# A due download for another domain can still dispatch => not reached.
|
||||
allowed = Download(url='https://rumble.com/new', downloader=name, status='new')
|
||||
test_session.add(allowed)
|
||||
test_session.commit()
|
||||
assert test_download_manager.get_summary()['daily_limit_reached'] is False
|
||||
|
||||
# Recurring downloads are never gated by daily limits and must not count as "due".
|
||||
test_session.delete(allowed)
|
||||
recurring = Download(url='https://example.com/feed', downloader=name, status='new',
|
||||
frequency=DownloadFrequency.daily)
|
||||
test_session.add(recurring)
|
||||
test_session.commit()
|
||||
assert test_download_manager.get_summary()['daily_limit_reached'] is True
|
||||
|
||||
# Global limit blocks everything, even a fresh domain.
|
||||
config.download_daily_limit_per_domain = None
|
||||
config.download_daily_limit_global = 2
|
||||
fresh = Download(url='https://wikipedia.org/new', downloader=name, status='new')
|
||||
test_session.add(fresh)
|
||||
test_session.commit()
|
||||
assert test_download_manager.get_summary()['daily_limit_reached'] is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_daily_limit_global_blocks(test_session, test_download_manager, test_downloader):
|
||||
"""The global daily limit caps downloads across all domains."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue