mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 10:51:36 -04:00
Adding download window which can prevent downloads from running at unwanted times.
This commit is contained in:
parent
4579562be5
commit
35994bd363
10 changed files with 395 additions and 57 deletions
122
app/cypress/e2e/settings/download-window.cy.js
Normal file
122
app/cypress/e2e/settings/download-window.cy.js
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
describe('Download Window Settings', () => {
|
||||
const mockSettings = {
|
||||
archive_destination: 'archive/%(domain_tag)s/%(domain)s',
|
||||
download_manager_disabled: false,
|
||||
download_manager_stopped: false,
|
||||
download_on_startup: true,
|
||||
download_timeout: 0,
|
||||
download_wait: 20,
|
||||
download_window_start: null,
|
||||
download_window_end: null,
|
||||
hotspot_device: 'wlan0',
|
||||
hotspot_on_startup: true,
|
||||
hotspot_password: 'wrolpi hotspot',
|
||||
hotspot_ssid: 'WROLPi',
|
||||
check_for_upgrades: true,
|
||||
ignore_outdated_zims: false,
|
||||
log_level: 'info',
|
||||
map_destination: 'map',
|
||||
nav_color: 'violet',
|
||||
media_directory: '/media/wrolpi',
|
||||
tags_directory: true,
|
||||
throttle_on_startup: false,
|
||||
version: '1.0.0',
|
||||
videos_destination: 'videos/%(channel_tag)s/%(channel_name)s',
|
||||
wrol_mode: false,
|
||||
zims_destination: 'zims',
|
||||
save_ffprobe_json: true,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
cy.intercept('GET', '/api/status', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
version: '1.0.0',
|
||||
flags: {},
|
||||
cpu_percent: 10,
|
||||
memory_percent: 30,
|
||||
downloads: {pending: 0, recurring: 0, disabled: false, stopped: false, outside_download_window: false},
|
||||
}
|
||||
}).as('getStatus');
|
||||
|
||||
cy.intercept('GET', '/api/tags', {
|
||||
statusCode: 200,
|
||||
body: {tags: []}
|
||||
}).as('getTags');
|
||||
|
||||
cy.intercept('GET', '/api/events', {
|
||||
statusCode: 200,
|
||||
body: {events: []}
|
||||
}).as('getEvents');
|
||||
|
||||
cy.intercept('POST', '/api/search/suggestions', {
|
||||
statusCode: 200,
|
||||
body: {fileGroups: 0, zimsEstimates: [], channels: [], domains: []}
|
||||
}).as('getSuggestions');
|
||||
});
|
||||
|
||||
it('time inputs are empty when no window is configured', () => {
|
||||
cy.intercept('GET', '/api/settings', {
|
||||
statusCode: 200,
|
||||
body: {...mockSettings},
|
||||
}).as('getSettings');
|
||||
|
||||
cy.visit('/admin/settings');
|
||||
cy.wait('@getSettings');
|
||||
|
||||
cy.get('input[type="time"]').should('have.length', 2);
|
||||
cy.get('input[type="time"]').first().should('have.value', '');
|
||||
cy.get('input[type="time"]').last().should('have.value', '');
|
||||
});
|
||||
|
||||
it('displays saved download window values from API', () => {
|
||||
cy.intercept('GET', '/api/settings', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
...mockSettings,
|
||||
download_window_start: '08:00',
|
||||
download_window_end: '17:00',
|
||||
},
|
||||
}).as('getSettings');
|
||||
|
||||
cy.visit('/admin/settings');
|
||||
cy.wait('@getSettings');
|
||||
|
||||
cy.get('input[type="time"]').should('have.length', 2);
|
||||
cy.get('input[type="time"]').first().should('have.value', '08:00');
|
||||
cy.get('input[type="time"]').last().should('have.value', '17:00');
|
||||
});
|
||||
|
||||
it('can set download window values', () => {
|
||||
cy.intercept('GET', '/api/settings', {
|
||||
statusCode: 200,
|
||||
body: {...mockSettings},
|
||||
}).as('getSettings');
|
||||
|
||||
cy.visit('/admin/settings');
|
||||
cy.wait('@getSettings');
|
||||
|
||||
cy.get('input[type="time"]').first().type('08:00');
|
||||
cy.get('input[type="time"]').first().should('have.value', '08:00');
|
||||
|
||||
cy.get('input[type="time"]').last().type('17:00');
|
||||
cy.get('input[type="time"]').last().should('have.value', '17:00');
|
||||
});
|
||||
|
||||
it('displays overnight window values from API', () => {
|
||||
cy.intercept('GET', '/api/settings', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
...mockSettings,
|
||||
download_window_start: '22:00',
|
||||
download_window_end: '06:00',
|
||||
},
|
||||
}).as('getSettings');
|
||||
|
||||
cy.visit('/admin/settings');
|
||||
cy.wait('@getSettings');
|
||||
|
||||
cy.get('input[type="time"]').first().should('have.value', '22:00');
|
||||
cy.get('input[type="time"]').last().should('have.value', '06:00');
|
||||
});
|
||||
});
|
||||
|
|
@ -302,6 +302,8 @@ export async function getSettings() {
|
|||
download_on_startup: content.download_on_startup,
|
||||
download_timeout: content.download_timeout,
|
||||
download_wait: content.download_wait,
|
||||
download_window_start: content.download_window_start,
|
||||
download_window_end: content.download_window_end,
|
||||
hotspot_device: content.hotspot_device,
|
||||
hotspot_on_startup: content.hotspot_on_startup,
|
||||
hotspot_password: content.hotspot_password,
|
||||
|
|
|
|||
|
|
@ -591,6 +591,17 @@ export function WROLModeMessage({content}) {
|
|||
return null;
|
||||
}
|
||||
|
||||
export function DownloadWindowMessage() {
|
||||
const {status} = React.useContext(StatusContext);
|
||||
const downloads = status ? status.downloads : null;
|
||||
|
||||
if (downloads && downloads.outside_download_window) {
|
||||
return <Message icon='clock outline' header='Outside Download Window'
|
||||
content='Downloads are paused because the current time is outside the configured download window.'/>
|
||||
}
|
||||
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,
|
||||
DownloadWindowMessage,
|
||||
ErrorMessage,
|
||||
formatFrequency,
|
||||
humanBandwidth,
|
||||
|
|
@ -681,6 +682,7 @@ export function DownloadsPage() {
|
|||
|
||||
return <>
|
||||
<WROLModeMessage content='Downloads are disabled because WROL Mode is enabled.'/>
|
||||
<DownloadWindowMessage/>
|
||||
<DisableDownloadsToggle/>
|
||||
<CookiesLockedMessage/>
|
||||
|
||||
|
|
|
|||
|
|
@ -443,6 +443,8 @@ export function SettingsPage() {
|
|||
download_on_startup: settings.download_on_startup,
|
||||
download_timeout: settings.download_timeout,
|
||||
download_wait: settings.download_wait,
|
||||
download_window_start: settings.download_window_start || '',
|
||||
download_window_end: settings.download_window_end || '',
|
||||
hotspot_device: settings.hotspot_device,
|
||||
hotspot_on_startup: settings.hotspot_on_startup,
|
||||
hotspot_password: settings.hotspot_password,
|
||||
|
|
@ -562,34 +564,46 @@ export function SettingsPage() {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<Form.Group inline>
|
||||
<Form.Input
|
||||
label={<>
|
||||
<b>Wait Between Downloads</b>
|
||||
<InfoPopup
|
||||
content='Number of seconds to wait after each download completes. This helps download "nicely" by being polite to servers. Set to 0 to disable waiting.'/>
|
||||
</>}
|
||||
value={state.download_wait}
|
||||
disabled={disabled || state.download_wait === null}
|
||||
onChange={(e, i) => handleTimeoutChange(e, 'download_wait', i.value)}
|
||||
/>
|
||||
</Form.Group>
|
||||
<br/>
|
||||
|
||||
<Form.Group inline>
|
||||
<Form.Input
|
||||
label={<>
|
||||
<b>Download Timeout</b>
|
||||
<InfoPopup content='Downloads will be stopped after this many seconds have elapsed.
|
||||
Downloads will never timeout if this is empty.'/>
|
||||
</>}
|
||||
value={state.download_timeout}
|
||||
disabled={disabled || state.download_timeout === null}
|
||||
onChange={(e, i) => handleTimeoutChange(e, 'download_timeout', i.value)}
|
||||
<label htmlFor='log_levels_input'>Log Level: {logLevelToName(state.log_level)}</label>
|
||||
<br/>
|
||||
<input type='range'
|
||||
id='log_levels_input'
|
||||
list='log_levels'
|
||||
min='1'
|
||||
max='5'
|
||||
value={state.log_level}
|
||||
onChange={e => setState({...state, log_level: parseInt(e.target.value)})}
|
||||
style={{marginBottom: '1em'}}
|
||||
/>
|
||||
<datalist id='log_levels'>
|
||||
<option value='1'>Critical</option>
|
||||
<option value='2'>Warning</option>
|
||||
<option value='3'>Info</option>
|
||||
<option value='4'>Debug</option>
|
||||
<option value='5'>Trace</option>
|
||||
</datalist>
|
||||
|
||||
<br/>
|
||||
|
||||
<ButtonGroup>
|
||||
<Button color={state.nav_color} onClick={e => e.preventDefault()}>Navbar Color</Button>
|
||||
<Dropdown
|
||||
id='settings_navbar_color_dropdown'
|
||||
className='button icon'
|
||||
floating
|
||||
options={navColorOptions}
|
||||
onChange={(e, {value}) => setState({...state, nav_color: value})}
|
||||
value={state.nav_color}
|
||||
/>
|
||||
</Form.Group>
|
||||
</ButtonGroup>
|
||||
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<Segment>
|
||||
<Header as='h3'>Hotspot Settings</Header>
|
||||
|
||||
<Form.Input
|
||||
label='Hotspot SSID'
|
||||
value={state.hotspot_ssid}
|
||||
|
|
@ -638,40 +652,61 @@ export function SettingsPage() {
|
|||
</Grid>
|
||||
|
||||
|
||||
<br/>
|
||||
<Divider/>
|
||||
<Header as='h3'>Download settings</Header>
|
||||
|
||||
<label htmlFor='log_levels_input'>Log Level: {logLevelToName(state.log_level)}</label>
|
||||
<br/>
|
||||
<input type='range'
|
||||
id='log_levels_input'
|
||||
list='log_levels'
|
||||
min='1'
|
||||
max='5'
|
||||
value={state.log_level}
|
||||
onChange={e => setState({...state, log_level: parseInt(e.target.value)})}
|
||||
style={{marginBottom: '1em'}}
|
||||
/>
|
||||
<datalist id='log_levels'>
|
||||
<option value='1'>Critical</option>
|
||||
<option value='2'>Warning</option>
|
||||
<option value='3'>Info</option>
|
||||
<option value='4'>Debug</option>
|
||||
<option value='5'>Trace</option>
|
||||
</datalist>
|
||||
|
||||
<br/>
|
||||
|
||||
<ButtonGroup>
|
||||
<Button color={state.nav_color} onClick={e => e.preventDefault()}>Navbar Color</Button>
|
||||
<Dropdown
|
||||
id='settings_navbar_color_dropdown'
|
||||
className='button icon'
|
||||
floating
|
||||
options={navColorOptions}
|
||||
onChange={(e, {value}) => setState({...state, nav_color: value})}
|
||||
value={state.nav_color}
|
||||
<Form.Group inline>
|
||||
<Form.Input
|
||||
label={<>
|
||||
<b>Wait Between Downloads</b>
|
||||
<InfoPopup
|
||||
content='Number of seconds to wait after each download completes. This helps download "nicely" by being polite to servers. Set to 0 to disable waiting.'/>
|
||||
</>}
|
||||
value={state.download_wait}
|
||||
disabled={disabled || state.download_wait === null}
|
||||
onChange={(e, i) => handleTimeoutChange(e, 'download_wait', i.value)}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</Form.Group>
|
||||
<Form.Group inline>
|
||||
<Form.Input
|
||||
label={<>
|
||||
<b>Download Timeout</b>
|
||||
<InfoPopup content='Downloads will be stopped after this many seconds have elapsed.
|
||||
Downloads will never timeout if this is empty.'/>
|
||||
</>}
|
||||
value={state.download_timeout}
|
||||
disabled={disabled || state.download_timeout === null}
|
||||
onChange={(e, i) => handleTimeoutChange(e, 'download_timeout', i.value)}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group inline>
|
||||
<Form.Input
|
||||
label={<>
|
||||
<b>Download Window Start</b>
|
||||
<InfoPopup
|
||||
content='Start time for allowed download window (HH:MM, 24-hour). Leave both empty for no restriction.'/>
|
||||
</>}
|
||||
type='time'
|
||||
value={state.download_window_start}
|
||||
disabled={disabled}
|
||||
onChange={(e, i) => handleInputChange(e, 'download_window_start', i.value)}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group inline>
|
||||
<Form.Input
|
||||
label={<>
|
||||
<b>Download Window End</b>
|
||||
<InfoPopup
|
||||
content='End time for allowed download window (HH:MM, 24-hour). Leave both empty for no restriction.'/>
|
||||
</>}
|
||||
type='time'
|
||||
value={state.download_window_end}
|
||||
disabled={disabled}
|
||||
onChange={(e, i) => handleInputChange(e, 'download_window_end', i.value)}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Divider/>
|
||||
|
||||
|
|
|
|||
|
|
@ -792,6 +792,8 @@ class WROLPiConfigValidator:
|
|||
download_on_startup: bool = None
|
||||
download_timeout: int = None
|
||||
download_wait: int = None
|
||||
download_window_start: str = None
|
||||
download_window_end: str = None
|
||||
hotspot_device: str = None
|
||||
hotspot_on_startup: bool = None
|
||||
hotspot_password: str = None
|
||||
|
|
@ -932,6 +934,8 @@ class WROLPiConfig(ConfigFile):
|
|||
download_on_startup=True,
|
||||
download_timeout=0,
|
||||
download_wait=20,
|
||||
download_window_start=None,
|
||||
download_window_end=None,
|
||||
hotspot_device='wlan0',
|
||||
hotspot_on_startup=True,
|
||||
hotspot_password='wrolpi hotspot',
|
||||
|
|
@ -1018,6 +1022,22 @@ class WROLPiConfig(ConfigFile):
|
|||
def download_wait(self, value: int):
|
||||
self.update({'download_wait': value})
|
||||
|
||||
@property
|
||||
def download_window_start(self) -> str:
|
||||
return self._config.get('download_window_start')
|
||||
|
||||
@download_window_start.setter
|
||||
def download_window_start(self, value: str):
|
||||
self.update({'download_window_start': value})
|
||||
|
||||
@property
|
||||
def download_window_end(self) -> str:
|
||||
return self._config.get('download_window_end')
|
||||
|
||||
@download_window_end.setter
|
||||
def download_window_end(self, value: str):
|
||||
self.update({'download_window_end': value})
|
||||
|
||||
@property
|
||||
def hotspot_device(self) -> str:
|
||||
return self._config['hotspot_device']
|
||||
|
|
|
|||
|
|
@ -423,15 +423,18 @@ class Downloader:
|
|||
try:
|
||||
while not task.done():
|
||||
if download_manager.download_is_killed(download_id) or not download_manager.can_download:
|
||||
outside_window = download_manager.outside_download_window
|
||||
logger.warning(f'Cancel download of {download.url}')
|
||||
task.cancel()
|
||||
try:
|
||||
await task
|
||||
except asyncio.CancelledError as e:
|
||||
logger.info(f'Successful cancel of {download.url}', exc_info=e)
|
||||
error = 'Download paused: outside download window' if outside_window \
|
||||
else 'Download was canceled'
|
||||
return DownloadResult(
|
||||
success=False,
|
||||
error='Download was canceled',
|
||||
error=error,
|
||||
)
|
||||
finally:
|
||||
download_manager.unkill_download(download_id)
|
||||
|
|
@ -592,6 +595,28 @@ class DownloadManager:
|
|||
self.stopped.clear()
|
||||
self.disabled.clear()
|
||||
|
||||
def is_within_download_window(self) -> bool:
|
||||
"""Returns True if current time is within the configured download window, or no window is configured."""
|
||||
config = get_wrolpi_config()
|
||||
start = config.download_window_start
|
||||
end = config.download_window_end
|
||||
if not start or not end:
|
||||
return True
|
||||
|
||||
current = now().time()
|
||||
start_time = datetime.strptime(start, "%H:%M").time()
|
||||
end_time = datetime.strptime(end, "%H:%M").time()
|
||||
|
||||
if start_time <= end_time:
|
||||
return start_time <= current < end_time
|
||||
else:
|
||||
# Overnight window (e.g., 22:00-06:00)
|
||||
return current >= start_time or current < end_time
|
||||
|
||||
@property
|
||||
def outside_download_window(self) -> bool:
|
||||
return not self.is_within_download_window()
|
||||
|
||||
@property
|
||||
def can_download(self) -> bool:
|
||||
"""Returns True only if all steps necessary for downloading have been met."""
|
||||
|
|
@ -609,6 +634,9 @@ class DownloadManager:
|
|||
if wrol_mode_enabled():
|
||||
# Do not download with WROL Mode enabled.
|
||||
return False
|
||||
if not self.is_within_download_window():
|
||||
# Do not download outside the configured time window.
|
||||
return False
|
||||
if get_download_manager_config().successful_import:
|
||||
# Finally, allow downloading because config was valid and imported.
|
||||
return True
|
||||
|
|
@ -1158,6 +1186,7 @@ class DownloadManager:
|
|||
recurring=counts['recurring_downloads'],
|
||||
disabled=self.is_disabled,
|
||||
stopped=self.is_stopped,
|
||||
outside_download_window=self.outside_download_window,
|
||||
)
|
||||
return summary
|
||||
|
||||
|
|
@ -1424,7 +1453,11 @@ async def signal_download_download(download_id: int, download_url: str):
|
|||
download_manager.create_downloads(session, urls, downloader_name=download.sub_downloader,
|
||||
settings=child_settings)
|
||||
|
||||
if try_again is False and not download.frequency:
|
||||
if result.error and 'outside download window' in result.error:
|
||||
# Download was paused due to window closing; reset to new so it resumes later.
|
||||
download.renew()
|
||||
download.error = result.error
|
||||
elif try_again is False and not download.frequency:
|
||||
# Only once-downloads can fail.
|
||||
download.fail()
|
||||
elif result.success:
|
||||
|
|
|
|||
|
|
@ -157,6 +157,8 @@ def get_settings(_: Request):
|
|||
'download_on_startup': wrolpi_config.download_on_startup,
|
||||
'download_timeout': wrolpi_config.download_timeout,
|
||||
'download_wait': wrolpi_config.download_wait,
|
||||
'download_window_start': wrolpi_config.download_window_start,
|
||||
'download_window_end': wrolpi_config.download_window_end,
|
||||
'hotspot_device': wrolpi_config.hotspot_device,
|
||||
'hotspot_on_startup': wrolpi_config.hotspot_on_startup,
|
||||
'hotspot_password': wrolpi_config.hotspot_password,
|
||||
|
|
@ -230,6 +232,17 @@ async def update_settings(_: Request, body: schema.SettingsRequest):
|
|||
elif not body.zims_destination:
|
||||
new_config['zims_destination'] = wrolpi_config.default_config['zims_destination']
|
||||
|
||||
# Handle download window fields: empty string clears the value.
|
||||
for field_name in ('download_window_start', 'download_window_end'):
|
||||
value = getattr(body, field_name, None)
|
||||
if value is not None:
|
||||
if value == '':
|
||||
new_config[field_name] = None
|
||||
else:
|
||||
if not re.match(r'^\d{2}:\d{2}$', value):
|
||||
raise InvalidConfig(f'{field_name} must be in HH:MM format')
|
||||
new_config[field_name] = value
|
||||
|
||||
log_level = new_config.pop('log_level', None)
|
||||
if isinstance(log_level, int):
|
||||
set_global_log_level(log_level)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ class SettingsResponse:
|
|||
download_on_startup: bool
|
||||
download_timeout: int
|
||||
download_wait: int
|
||||
download_window_start: Optional[str]
|
||||
download_window_end: Optional[str]
|
||||
hotspot_device: str
|
||||
hotspot_on_startup: bool
|
||||
hotspot_password: str
|
||||
|
|
@ -61,6 +63,8 @@ class SettingsRequest:
|
|||
download_on_startup: Optional[bool] = None
|
||||
download_timeout: Optional[int] = None
|
||||
download_wait: Optional[int] = None
|
||||
download_window_start: Optional[str] = None
|
||||
download_window_end: Optional[str] = None
|
||||
hotspot_device: Optional[str] = None
|
||||
hotspot_on_startup: Optional[bool] = None
|
||||
hotspot_password: Optional[str] = None
|
||||
|
|
|
|||
|
|
@ -1042,3 +1042,99 @@ def test_yaml_dump_keeps_overrides(test_session, test_download_manager, test_dow
|
|||
assert settings['writesubtitles'] is False, 'Non-default writesubtitles should be kept'
|
||||
finally:
|
||||
set_test_downloader_config(False)
|
||||
|
||||
|
||||
def test_is_within_download_window_no_config(test_download_manager):
|
||||
"""Returns True when no download window is configured."""
|
||||
config = get_wrolpi_config()
|
||||
config.download_window_start = None
|
||||
config.download_window_end = None
|
||||
assert test_download_manager.is_within_download_window() is True
|
||||
|
||||
# Only one field set should also return True (no restriction).
|
||||
config.download_window_start = '08:00'
|
||||
config.download_window_end = None
|
||||
assert test_download_manager.is_within_download_window() is True
|
||||
|
||||
config.download_window_start = None
|
||||
config.download_window_end = '17:00'
|
||||
assert test_download_manager.is_within_download_window() is True
|
||||
|
||||
|
||||
def test_is_within_download_window_same_day(test_download_manager, fake_now):
|
||||
"""Same-day window (e.g. 08:00-17:00) boundary checks."""
|
||||
config = get_wrolpi_config()
|
||||
config.download_window_start = '08:00'
|
||||
config.download_window_end = '17:00'
|
||||
|
||||
# Before window
|
||||
fake_now(datetime(2000, 1, 1, 7, 59))
|
||||
assert test_download_manager.is_within_download_window() is False
|
||||
|
||||
# Start of window
|
||||
fake_now(datetime(2000, 1, 1, 8, 0))
|
||||
assert test_download_manager.is_within_download_window() is True
|
||||
|
||||
# Middle of window
|
||||
fake_now(datetime(2000, 1, 1, 12, 0))
|
||||
assert test_download_manager.is_within_download_window() is True
|
||||
|
||||
# End of window (exclusive)
|
||||
fake_now(datetime(2000, 1, 1, 17, 0))
|
||||
assert test_download_manager.is_within_download_window() is False
|
||||
|
||||
# After window
|
||||
fake_now(datetime(2000, 1, 1, 23, 0))
|
||||
assert test_download_manager.is_within_download_window() is False
|
||||
|
||||
|
||||
def test_is_within_download_window_overnight(test_download_manager, fake_now):
|
||||
"""Overnight window (e.g. 22:00-06:00) wrapping."""
|
||||
config = get_wrolpi_config()
|
||||
config.download_window_start = '22:00'
|
||||
config.download_window_end = '06:00'
|
||||
|
||||
# Before window (daytime)
|
||||
fake_now(datetime(2000, 1, 1, 12, 0))
|
||||
assert test_download_manager.is_within_download_window() is False
|
||||
|
||||
# Just before window start
|
||||
fake_now(datetime(2000, 1, 1, 21, 59))
|
||||
assert test_download_manager.is_within_download_window() is False
|
||||
|
||||
# Start of window
|
||||
fake_now(datetime(2000, 1, 1, 22, 0))
|
||||
assert test_download_manager.is_within_download_window() is True
|
||||
|
||||
# Middle of night
|
||||
fake_now(datetime(2000, 1, 2, 3, 0))
|
||||
assert test_download_manager.is_within_download_window() is True
|
||||
|
||||
# End of window (exclusive)
|
||||
fake_now(datetime(2000, 1, 2, 6, 0))
|
||||
assert test_download_manager.is_within_download_window() is False
|
||||
|
||||
|
||||
def test_download_window_renews_download(test_session, test_download_manager, test_downloader):
|
||||
"""A download stopped by the window closing is renewed to 'new' status."""
|
||||
from wrolpi.downloader import DownloadResult
|
||||
download = test_download_manager.create_download(test_session, 'https://example.com/window', test_downloader.name)
|
||||
download.started()
|
||||
test_session.commit()
|
||||
|
||||
# Simulate the result that cancel_wrapper returns when outside the window.
|
||||
result = DownloadResult(success=False, error='Download paused: outside download window')
|
||||
|
||||
# Apply the same logic as signal_download_download.
|
||||
download.location = result.location or download.location or None
|
||||
download.error = result.error if result.error else None
|
||||
|
||||
if result.error and 'outside download window' in result.error:
|
||||
download.renew()
|
||||
download.error = result.error
|
||||
|
||||
test_session.commit()
|
||||
test_session.refresh(download)
|
||||
|
||||
assert download.status == 'new'
|
||||
assert 'outside download window' in download.error
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue